frontend · level 1

HTML Semantics

Structure that assistive technology can read.

120 XP

HTML Semantics

Every element you write generates an accessibility tree alongside the visual DOM. Screen readers, search crawlers, and browser features like Reader Mode all work from that tree — not from your CSS. If your structure is built from <div> elements, those tools see an empty document.

Analogy

Think of a hotel with fire evacuation diagrams posted by every elevator. The diagram labels each room: "guest suite", "stairwell", "exit", "fire extinguisher". A guest who can see simply looks around — they can tell the lobby from a storage closet by the couches and the chandelier. A guest in the dark only has the diagram. If every room on the diagram is marked "room", the evacuation guide is useless, even if the building looks impressive from the lobby. <div> is the unlabeled room; <nav>, <main>, and <button> are the labels that make the building navigable when you cannot see it.

The accessibility tree

The browser builds a parallel representation of your page. Each element maps to a node with a role, an accessible name, and a state. Native HTML elements supply these automatically:

Element Implicit role What assistive tech announces
<button> button "Submit, button"
<nav> navigation "Navigation, landmark"
<h2> heading level 2 "Section title, heading level 2"
<main> main "Main, landmark"
<article> article "Article"
<div> (none) nothing

A <div> has no implicit role. Wrap your page in divs and assistive technology skips straight to the text nodes with no context.

Landmarks first

Landmark elements give the page its skeleton. A screen reader user can jump between them the same way a sighted user skims the visual layout.

<header>   <!-- banner landmark -->
  <nav>…</nav>
</header>

<main>     <!-- main landmark — exactly one per page -->
  <article>
    <h1>Post title</h1>
    …
  </article>
  <aside>Related links</aside>
</main>

<footer>   <!-- contentinfo landmark -->
  …
</footer>

Every page needs exactly one <main>. Everything else can repeat, but <main> is the anchor that screen readers jump to with a single keystroke.

Heading order

Headings encode document outline. <h1> is the page title; <h2> starts top-level sections; <h3> sub-sections. The rule: never skip a level on the way down.

<!-- correct -->
<h1>Products</h1>
  <h2>Accessories</h2>
    <h3>Cables</h3>

<!-- wrong — jumps from h1 to h3 -->
<h1>Products</h1>
  <h3>Accessories</h3>

Skipping breaks the outline. Screen reader users navigate headings by level — a jump from h1 to h3 makes the structure invisible to them.

The first rule of ARIA

If you can use a native HTML element or attribute with the semantics and behaviour you require already built in, then do so.

ARIA attributes (role, aria-label, aria-expanded, …) exist for cases where native HTML falls short — custom dropdowns, comboboxes, disclosure widgets. Adding role="button" to a real <button> is noise, not improvement:

<!-- redundant — <button> already has role="button" -->
<button role="button">Submit</button>

<!-- correct use — a div acting as a button -->
<div role="button" tabindex="0">Submit</div>

The second example is still worse than a real <button> — you lose keyboard handling and focus styling for free — but ARIA at least makes the intent visible to assistive tech.

When ARIA helps

Custom interactive widgets that have no HTML equivalent need ARIA:

  • A custom role="combobox" with aria-expanded and aria-autocomplete
  • A role="tabpanel" with aria-labelledby pointing at its tab
  • aria-live="polite" on a status region that updates after user action

ARIA is the escape hatch. Reach for it after native elements, not instead of them.

Practical checklist

  • One <h1> per page. Levels increase by one.
  • <main>, <header>, <footer>, <nav> in place of structural <div> wrappers.
  • Every <img> has alt — empty string for decorative images.
  • Every form control is <label>-associated.
  • Interactive elements are focusable and keyboard-operable without ARIA.

Tools in the wild

6 tools
  • axe DevToolsfree tier

    Browser extension that audits semantics + accessibility issues against WCAG.

    service
  • WAVEfree tier

    WebAIM's accessibility evaluator; visually overlays semantic structure on the page.

    service
  • Lighthousefree tier

    Built into Chrome DevTools — accessibility + SEO audits flag missing landmarks/labels.

    cli
  • html-validatefree tier

    Configurable HTML linter for build pipelines — catches semantic mistakes pre-PR.

    library
  • macOS VoiceOver and Windows NVDA screen readers — the ground truth for semantic markup.

    cli
  • MDN ARIAfree tier

    Reference for `role=`, `aria-*` patterns when native HTML semantics aren't enough.

    spec