Frontend / Accessibility / 02_semantic_html_and_landmarks.md

Semantic HTML and Landmarks

Updated 4 min read source
On this page6
  1. TL;DR
  2. In depth
  3. Gotchas / edge cases
  4. What a senior is expected to say
  5. Cross-references
  6. Further reading

Semantic HTML and Landmarks

TL;DR

Semantic HTML is ~80% of accessibility for free. Native elements (<button>, <a>, <nav>, <main>, <h1><h6>, <label>, <table>) come with the correct role, keyboard behavior, focusability, and states that assistive tech relies on. A <div onClick> has none of that. Use the right element first; reach for ARIA only to fill genuine gaps. Landmarks (header, nav, main, aside, footer) let screen-reader users jump between page regions; a logical heading hierarchy is how they skim.

In depth

Why prefer <button> over <div onClick>?

A native <button> is, for free:

  • focusable (in the tab order) — a <div> is not without tabindex="0",
  • operable by keyboard — Enter/Space activate it; a <div> needs manual keydown handling,
  • announced as “button” with its label by screen readers — a <div> is announced as nothing,
  • equipped with disabled semantics and form participation.
html
<!-- Faking it, and still not equivalent. -->
<div role="button" tabindex="0"
     @click="save"
     @keydown.enter="save"
     @keydown.space.prevent="save">
  Save
</div>

<!-- All of that free, plus disabled and form submit. -->
<button type="submit">Save</button>

To match a <button> with a <div> you’d need role="button", tabindex="0", and key handlers for Enter and Space — and you’d still miss edge cases. Just use <button>.

What are landmarks and why do they matter?

Landmark elements define page regions that screen-reader users navigate between directly (e.g., “jump to main”):

Element Role Purpose
<header> banner (when top-level) site header
<nav> navigation navigation blocks
<main> main the primary content (one per page)
<aside> complementary tangential content
<footer> contentinfo (top-level) site footer
<section> with a name region named significant area
<form> with a name form a form region

Use one <main>. Give repeated landmarks accessible names so users can tell them apart:

html
<header>
  <nav aria-label="Primary">...</nav>
</header>

<main id="main">...</main>

<aside aria-label="Related articles">...</aside>

<footer>
  <nav aria-label="Footer">...</nav>
</footer>

Two unlabelled <nav> elements are announced identically, so the landmark list reads “navigation, navigation” and helps nobody. And <main id="main"> is what the skip link needs — <a href="#main" class="skip">Skip to content</a> as the first focusable element on the page.

How should headings be structured?

One <h1> describing the page, then a logical, non-skipping hierarchy (h1 → h2 → h3, don’t jump h2→h4). Headings are the screen-reader equivalent of skimming — users list and jump by heading. Don’t pick a heading level for its font size (style with CSS); pick it for document structure.

When is a <div>/<span> the right choice?

For styling/layout containers with no semantic meaning. <div> and <span> have no implicit role, which is correct when the element is purely presentational. Problems arise only when you put behavior (click handlers, “buttons,” “links”) on them.

What semantic elements are commonly under-used?

<button type="button"> (vs div), <a href> for navigation (vs button/div with JS), <label> tied to inputs, <fieldset>/<legend> for groups, <table> with <th scope> for tabular data, <ul>/<ol>/<li> for lists, <nav>/<main>/<aside>, <details>/<summary> for native disclosure, <dialog> for modals, <time>, <figure>/<figcaption>. Each carries semantics ARIA would otherwise have to fake.

<button> vs <a> — when each?

<a href> navigates (changes URL / location) and should be a link. <button> performs an action in place (submit, toggle, open menu). Misusing them breaks expectations: links open in new tabs, are bookmarkable, and respond to Enter; buttons respond to Enter and Space and aren’t navigable destinations. A “link” with role="button" is usually a sign you picked the wrong element.

Gotchas / edge cases

  • tabindex > 0 is an anti-pattern — it jumps the element ahead of natural order and creates confusing focus. Use 0 (in natural order) or -1 (focusable only programmatically).
  • role="presentation"/aria-hidden on interactive elements removes them from the a11y tree but they remain keyboard-focusable — a trap. Don’t hide focusable things.
  • Headings used for size produce nonsense outlines for screen readers — keep level = structure, size = CSS.
  • Multiple <main> or <h1> confuses landmark/heading navigation.
  • Generic link text (“click here”, “read more” ×10) is useless out of context — screen-reader users list links; make them self-describing.
  • <section> without an accessible name is not exposed as a landmark — name it or use a plain element.

What a senior is expected to say 4

  • “Semantic HTML gives role, keyboard behavior, focusability, and state for free — <div onClick> gives none. Right element first, ARIA only for gaps.”
  • “Landmarks let screen-reader users jump between regions; one <main>, named duplicate <nav>s.”
  • “Headings are document structure for skimming — level by meaning, size by CSS, no skipped levels.”
  • <a> navigates, <button> acts; positive tabindex is an anti-pattern.”

Cross-references

Further reading