Lesson 1 of 6
HTML & Semantics
Give your markup meaning: the right <button>, <a>, and landmarks like <nav>, <main>, and <article> make pages readable to screen readers and search engines alike.
① Watch
Speed
📖 Read this walkthrough — every command, and why
HTML & Semantics — real tags become real accessibility roles
Mental model: a browser doesn't render your markup the way you see it. It builds a
live tree — the DOM — from your tags, and that same tree is what a screen reader and
a search engine actually read. The element you pick decides what that tree MEANS.
Semantic elements (header, nav, main, footer, h1..h6, button, img with alt) each carry
a built-in ROLE; a plain div carries none. So structure and accessibility come along
for free from the right tags, and div-soup throws all of it away.
The markup we inspected
-----------------------
<header>
<h1>Sprkd</h1>
</header>
<nav>
<a href="/docs">Docs</a>
</nav>
<main>
<h2>Lessons</h2>
<button>Start</button>
<img src="thumb.png" alt="course thumbnail">
<img src="spacer.png"> <!-- no alt -->
</main>
<footer>
© 2026
</footer>
The accessibility tree Chromium actually exposed
------------------------------------------------
This is the real snapshot a screen reader consumes — captured from a live Chromium
render, not invented:
RootWebArea
banner
heading "Sprkd"
navigation
link "Docs"
StaticText "Docs"
main
heading "Lessons"
button "Start"
image "course thumbnail"
StaticText " "
contentinfo
StaticText "© 2026"
landmark elements present: {"banner":true,"nav":true,"main":true,"contentinfo":true}
Read it element by element:
<header> -> banner landmark (you wrote zero ARIA to get this)
<nav> -> navigation landmark
<main> -> main landmark
<footer> -> contentinfo landmark
<h1>/<h2> -> heading role
<button> -> button role
<a href> -> link role, with accessible name from its text
<img alt="course thumbnail"> -> image, accessible name = "course thumbnail"
<img> with NO alt -> appears with NO accessible name (invisible to a screen reader)
Four landmark regions, and you never wrote a single line of code to create them.
Why the roles matter
--------------------
1 · Landmarks are free navigation
A screen reader user can jump straight to banner, navigation, main, or contentinfo
instead of arrowing through the whole page. The four semantic wrappers above gave
them four jump targets. Replace those with <div>s and there are NO landmarks to jump
to — the roles simply disappear from the tree.
2 · Headings form the document outline
h1 through h6 are the page's table of contents. Assistive tech lets users pull up a
heading list and skim it; search engines read the same outline to understand the
page. In the captured tree "Sprkd" and "Lessons" both surfaced with the heading role.
Keep the levels in order (don't jump h1 -> h4 for a font size) — the outline is
structural, not visual.
3 · Controls announce themselves
<button> came through as button "Start" — the role AND an accessible name from its
text. A screen reader announces "Start, button". A clickable <div> announces nothing
useful; you'd have to bolt on role="button", tabindex, and key handlers to fake what
the real element gives you for free.
4 · Images need alt to be perceivable — the sharp lesson
The two <img> tags landed in the tree very differently:
image "course thumbnail" <- had alt, so it has an accessible name to announce
(the alt-less <img>) <- NO accessible name — a screen reader can't describe it
A meaningful image with no alt is invisible to that user. Give every meaningful image
an alt that says what it conveys. (A purely decorative image is the one exception:
alt="" marks it decorative so the screen reader skips it — silence on purpose, not by
accident.)
How to inspect the a11y tree yourself
-------------------------------------
DevTools (Chrome/Edge):
Elements panel -> Accessibility tab -> "Full-page accessibility tree" (Enable, reload).
Select any node to see its computed Role and Name.
Headless, scriptable (Puppeteer) — how the snapshot above was captured:
const snapshot = await page.accessibility.snapshot();
console.log(JSON.stringify(snapshot, null, 2));
// roles: "banner","navigation","main","contentinfo","heading","button","link","image"
// name of the img-with-alt node === "course thumbnail"
Form labels (same principle as alt)
-----------------------------------
A bare <input> has no accessible name either. Tie a <label> to it so the control is
announced:
<label for="email">Email</label>
<input id="email" type="email">
The label's `for` matches the input's `id` -> the input's accessible name becomes "Email".
Without it, a screen reader reads "edit text" with no idea what it's for.
Notes
-----
- Semantic vs div: <header>/<nav>/<main>/<footer>/<button>/<h1..h6> carry roles and land
in the accessibility tree as landmarks and named controls. <div> and <span> carry NO
role and NO meaning — they are layout hooks only. Div-soup renders identically on screen
but produces an empty, unnavigable tree.
- alt is required for meaningful images: the alt-less <img> above had NO accessible name.
Meaningful image -> descriptive alt. Decorative image -> alt="" (explicitly skipped).
A missing alt is not the same as alt="" — one is a gap, the other is a deliberate choice.
- The DOM is live: the tree is built from your markup and updated as the page changes.
Screen readers AND search engines read this structure, so the tags you choose are the
meaning you ship.
Verify
------
1 · Open the page, DevTools -> Elements -> Accessibility -> enable the full-page tree.
2 · Confirm the four landmarks are present: banner, navigation, main, contentinfo.
3 · Select the image WITH alt: its Name should read "course thumbnail".
4 · Select the alt-less image: its Name should be empty (no accessible name) — that's the bug.
5 · Or in Puppeteer: `await page.accessibility.snapshot()` and check that the roles above
appear and the alt-less <img> has no `name`.
Gotcha: swapping any semantic wrapper for a <div> silently removes its landmark from the
tree — the page looks identical but a screen reader loses the jump target.