Some of your users can't see the screen, can't use a mouse, can't hear audio, or are using a phone in bright sunlight with one thumb. Accessibility means the page still works for them. Most of it isn't extra work — it's using HTML the way it was designed.
The accessibility tree
Browsers build a second tree alongside the DOM: the accessibility tree. For every meaningful element, it records:
- a role — button, link, heading, textbox, checkbox, list…
- a name — what to call it: "Save draft", "Email"
- state — checked, expanded, disabled, invalid
- whether it's focusable
Screen readers, voice control and switch devices read this tree, not the pixels. If your custom control looks like a button but the tree says "generic, no name", those users get nothing.
It looks like a button and works with a mouse. But browsers build a second tree for assistive technology, and in it this is an unnamed, unfocusable generic element. Keyboard users can't reach it; screen readers can't say what it is.
You can inspect the tree directly: Chrome and Firefox DevTools both have an Accessibility panel.
1. Use the right element
Native elements come with role, name, keyboard behaviour and states built in:
| for… | use | not |
|---|---|---|
| an action | <button> | <div onclick> |
| going somewhere | <a href> | <span onclick="location=…"> |
| page structure | <header> <nav> <main> <footer> | nested <div>s |
| headings | <h1>–<h6>, in order | bold text |
| lists | <ul> / <ol> + <li> | divs with bullets |
| data | <table> with <th> | a CSS grid of divs |
Screen-reader users navigate by headings and landmarks the way sighted users
scan a page. A sensible heading outline and a single <main> make a long
page navigable in seconds.
2. Give everything a name
- Images:
altdescribes what the image conveys. Decorative images getalt="", so they're skipped. An icon inside a button with text needs no alt of its own. - Icon-only buttons: they need a name, from visually hidden text or
aria-label="Close". - Form fields: a
<label for>tied to every input. A placeholder is not a label — it vanishes when you type, and is often low-contrast. - Links: "Read more" ten times on a page is useless out of context. Make the link text say where it goes.
3. Everything works from the keyboard
Put the mouse away and press Tab through the page:
- Can you reach every interactive element, in a sensible order?
- Can you see where focus is? Never remove the focus outline without
replacing it —
:focus-visibleshows it for keyboard users only. - Do Enter and Space activate things? (Free with
<button>.) - Does a modal trap focus inside it while open, close on Escape, and
return focus to the button that opened it? The native
<dialog>element withshowModal()handles most of this. - Is there a "Skip to content" link, so keyboard users don't tab through the whole navigation on every page?
Avoid positive tabindex values: they override the natural order and are
almost impossible to maintain. tabindex="0" (focusable in order) and -1
(focusable from script only) are the useful ones.
4. Enough contrast, not only colour
- Body text needs a contrast ratio of at least 4.5:1 against its background (WCAG AA), and large text 3:1. Light grey on white usually fails. DevTools shows the ratio in the colour picker.
- Don't use colour alone to carry meaning. A red border on an invalid field
needs a text error message too, tied to the input with
aria-describedby. - Respect user settings: text should survive 200% zoom without breaking the
layout, and animations should calm down under
prefers-reduced-motion.
5. ARIA: the last resort
ARIA attributes (role, aria-*) change what the accessibility tree says.
They don't add behaviour: role="button" doesn't make a div focusable or
respond to Enter. The first rule of ARIA is don't use ARIA if a native
element does the job.
Where it's genuinely needed:
- State on custom widgets:
aria-expandedon a menu button,aria-selectedon tabs,aria-current="page"on the active nav link. - Live regions:
aria-live="polite"on a container makes screen readers announce updates — "3 results found", "Saved" — without moving focus. - Relationships:
aria-describedbyfor hints and errors,aria-controlsfor what a button opens.
For complex widgets (tabs, comboboxes, tree views), follow the WAI-ARIA Authoring Practices patterns. They specify the roles, states and exact keyboard behaviour users expect.
Testing
- Keyboard: tab through every new feature. Five minutes catches a lot.
- Automated checks: axe DevTools or Lighthouse find missing names, low contrast, and invalid ARIA — roughly a third of real issues.
- A screen reader: VoiceOver (macOS, iOS), NVDA (Windows, free) or TalkBack (Android). Listen to a page once; it changes how you write HTML.
Accessibility also overlaps with everything else you care about: semantic HTML helps search engines, labels help autofill, and keyboard support helps power users. The DOM you write is the interface for all of them.