HTML Semantics
Structure that assistive technology can read.
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"witharia-expandedandaria-autocomplete - A
role="tabpanel"witharia-labelledbypointing 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>hasalt— 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- serviceaxe DevToolsfree tier
Browser extension that audits semantics + accessibility issues against WCAG.
- serviceWAVEfree tier
WebAIM's accessibility evaluator; visually overlays semantic structure on the page.
- cliLighthousefree tier
Built into Chrome DevTools — accessibility + SEO audits flag missing landmarks/labels.
- libraryhtml-validatefree tier
Configurable HTML linter for build pipelines — catches semantic mistakes pre-PR.
- cliVoiceOver / NVDAfree tier
macOS VoiceOver and Windows NVDA screen readers — the ground truth for semantic markup.
- specMDN ARIAfree tier
Reference for `role=`, `aria-*` patterns when native HTML semantics aren't enough.