Accessibility (a11y)
Status: 🟩 COMPLETE Last updated: 2026-06-19 Plain-English tagline: Building websites that work for everyone, including people who can’t see well, can’t use a mouse, or rely on screen readers. Often legally required, always the right thing, and almost always makes the site better for everyone.
In plain English
Accessibility (often abbreviated a11y — “a,” then 11 letters, then “y”) is the practice of designing and building websites so that people with disabilities can use them effectively. This includes:
- People who are blind or have low vision, who use screen readers (software that reads the page aloud)
- People who can’t use a mouse, who navigate with the keyboard only — Tab, arrow keys, Enter, Space
- People with motor impairments, who might use voice control or assistive switches
- People who are deaf or hard of hearing, who need captions for video/audio
- People with cognitive differences — autism, dyslexia, ADHD — for whom clear layout, plain language, and predictable interactions matter
- Everyone else — because accessible sites are also easier to use in sunny weather, with a broken arm, on a bad connection, in a noisy room
Accessibility is not a niche feature for “those people.” It’s a core quality of a well-built site, like performance or security. It’s often legally required (Section 508 in the US, EAA in the EU, AODA in Ontario, DDA in Australia). And it always makes the site better.
The good news: most accessibility wins come from using HTML correctly. Semantic HTML + a few extra habits gets you 80% of the way. Most a11y work isn’t fancy — it’s <button> instead of <div onclick>, alt text on images, proper headings, sufficient color contrast.
Why it matters
- 15-20% of people have some form of disability. Globally that’s over a billion users. Inaccessible sites lock them out.
- Lawsuits are common. In the US, Domino’s, Beyoncé’s website, Amazon, Target — countless companies have been sued (and lost) over inaccessible sites.
- SEO benefits. Google’s crawler is essentially a power user of accessibility — semantic markup, proper headings, alt text all help SEO.
- Better UX for everyone. Captions help people watching videos with sound off. Keyboard navigation helps power users. Clear focus indicators help people coming back to a tab. High contrast helps in sunlight.
- Future you. Aging, injury, situational impairment — at some point everyone benefits from accessibility.
The four WCAG principles — “POUR”
The international standard (WCAG — Web Content Accessibility Guidelines, currently 2.1, with 2.2 being adopted) organizes accessibility around four principles:
| Principle | Plain English |
|---|---|
| Perceivable | Users can perceive the information (alt text on images, captions on videos, sufficient color contrast) |
| Operable | Users can operate the interface (keyboard accessible, enough time to read, no seizure-triggering animations) |
| Understandable | Content and operation are understandable (clear language, predictable behavior, helpful errors) |
| Robust | Works with current and future assistive tech (valid HTML, ARIA where needed) |
Each principle has guidelines, and each guideline has testable success criteria at three levels: A (minimum), AA (the practical target — what most laws require), AAA (stretch).
Aim for WCAG 2.1 AA. That’s the de facto bar.
The 10 highest-leverage habits
In rough order of how much accessibility they buy:
1. Use semantic HTML
<button>, <a>, <nav>, <main>, <article>, <section>, <aside>, <header>, <footer>, <h1> through <h6>, <ul>/<ol>, <label>, <form>.
A <button> is keyboard-accessible by default, focusable, announceable by screen readers, has Enter/Space behavior built in. A <div onClick> has none of that — and adding it back is harder than just using the right element.
Rule of thumb: if a native HTML element fits, use it. Reaching for ARIA or custom elements is the last resort.
2. Add alt text to images
<img src="cat.jpg" alt="Orange tabby cat napping on a blue blanket">Every <img> must have an alt attribute. If the image is purely decorative (like a stock photo behind a hero section), use alt="" (empty string, intentionally). If the image conveys meaning, describe it concisely.
Don’t say “image of” or “picture of” — screen readers already announce that.
3. Use headings properly
Headings (<h1> to <h6>) form an outline of the page. Screen reader users navigate by jumping between headings.
- One
<h1>per page, typically the page title - Don’t skip levels.
<h1>→<h2>→<h3>, not<h1>→<h3> - Use headings semantically, not stylistically. Don’t use
<h2>for a styled subhead that isn’t actually a sub-section. Use CSS for styling instead.
4. Label every form input
<label for="email">Email</label>
<input id="email" type="email" name="email">Or wrap:
<label>
Email
<input type="email" name="email">
</label>Without labels, screen readers announce inputs as “edit text” with no context. Placeholders are not labels — they disappear when typing and have poor contrast.
5. Sufficient color contrast
WCAG AA requires:
- 4.5:1 for normal text (under 18pt or 14pt bold)
- 3:1 for large text (18pt+ or 14pt bold+)
- 3:1 for UI components and graphical objects (icons, form borders)
Tools:
- WebAIM Contrast Checker
- Stark for Figma
- Chrome DevTools shows the contrast ratio when you hover a color
“Gray on white” looks great in your design tool and fails accessibility. Test.
6. Keyboard navigability
Every interactive element should be reachable via Tab and operable with Enter / Space / arrow keys. Test by closing your mouse and trying.
- Visible focus indicators. Don’t
outline: noneon focus without replacing with something equivalent. Browsers’ default focus rings are there for a reason. - Logical tab order. Usually matches reading order. If you’ve moved elements visually with CSS, the tab order can become wrong.
- No keyboard traps. Pressing Tab should always eventually exit modals or widgets.
7. Don’t rely on color alone
A red error message and green success message look identical to color-blind users (8% of men). Pair color with icons, text, or shapes.
<!-- Bad: color only -->
<p style="color: red">Save failed</p>
<!-- Good: color + icon + text -->
<p class="text-red-600"><Icon name="error" /> Save failed: connection lost</p>8. Provide captions and transcripts for media
Videos need closed captions. Audio needs transcripts. Auto-generated captions (YouTube, etc.) are a starting point but rarely good enough for important content.
9. ARIA — used carefully
When semantic HTML isn’t enough, ARIA (Accessible Rich Internet Applications) attributes fill in the gaps. Common ones:
aria-label="..."— gives an element an accessible name when there’s no visible text (e.g. a button with only an icon)aria-labelledby="id"— references another element’s text as the namearia-describedby="id"— references descriptive textaria-hidden="true"— hides from screen readers (e.g. decorative icons)aria-expanded="true|false"— for collapsible elementsaria-live="polite"— announces dynamic updates without interrupting
The first rule of ARIA: don’t use ARIA. If a native element does the job, use it. ARIA is a patch for cases where you must use a div to do what a button does, or where you’re building a custom widget. Misused ARIA actively harms screen reader users.
10. Test with assistive tech
The single best a11y check: try the site with a screen reader.
- macOS: VoiceOver (Cmd+F5 to toggle)
- Windows: NVDA (free) or JAWS (paid)
- iOS: VoiceOver (Settings → Accessibility)
- Android: TalkBack
Five minutes navigating your site with a screen reader teaches you more than hours of reading guidelines.
Accessibility in React / Next.js / shadcn
The good news: modern UI tooling helps a lot.
- shadcn/ui is built on Radix UI, which handles keyboard navigation, focus management, ARIA roles, and screen reader announcements for complex components (modals, dropdowns, menus) by default.
- Next.js’s
<Link>preserves keyboard behavior of<a>. - Next.js’s
<Image>requiresalttext (TypeScript will complain if missing). - React has
eslint-plugin-jsx-a11y— catches common mistakes at edit time.
But you still need to:
- Add
alttext to every image - Use proper heading structure
- Label every form input
- Check color contrast
- Test keyboard navigation
- Test with a screen reader
A concrete example: an accessible button vs an inaccessible one
Inaccessible
<div onclick="save()" class="bg-blue-500 px-4 py-2 text-white rounded">
Save
</div>Problems:
- Not focusable with Tab
- No keyboard activation (Enter/Space)
- Screen readers don’t announce it as a button
- No
:focusstyles - No
disabledstate - Form won’t submit if it’s inside one
Accessible
<button
type="button"
onClick={save}
className="bg-blue-500 hover:bg-blue-600 focus:ring-2 focus:ring-blue-300
px-4 py-2 text-white rounded
disabled:opacity-50 disabled:cursor-not-allowed"
>
Save
</button>Everything works automatically. Tab focuses it. Enter or Space activates it. Screen readers say “Save, button.” Focus ring shows where you are. Disabled state styles AND blocks clicks. Took the same amount of typing.
Quick a11y audit checklist
Run through these on any new page:
- Page has one
<h1>matching the page title - Heading levels don’t skip
- Every
<img>hasalt(empty for decorative) - Every
<input>has an associated<label> - Color contrast meets WCAG AA (4.5:1 for text)
- Site is fully keyboard-navigable (try with mouse closed)
- Focus indicators are visible everywhere
- Forms have clear, useful error messages
- No information is conveyed by color alone
- Dynamic updates announce themselves to screen readers
- Page works with browser zoom up to 200%
- Site reads sensibly with a screen reader
Tools
- Browser DevTools → Lighthouse — runs an accessibility audit (catches the easy stuff)
- axe DevTools (browser extension) — more thorough than Lighthouse
- WAVE (WebAIM) — another browser extension, very visual
- Polypane — a browser specifically for responsive + accessibility testing
- eslint-plugin-jsx-a11y — catches JSX issues at edit time
- Storybook a11y addon — tests components in isolation
Automated tools catch maybe 30-50% of issues. Manual testing — especially with a screen reader and a keyboard — catches the rest.
Common gotchas
-
outline: nonewithout a replacement. Removes focus indicators globally. Replace with a custom focus ring or don’t touch the outline. -
Placeholder as label. Looks clean, fails accessibility. Use real
<label>elements. -
Skipping heading levels to control visual size. Use CSS for size; use semantic levels for structure.
-
Empty links and buttons. A
<button><Icon /></button>with no text has no accessible name. Addaria-label="Save"(or visible text). -
role="button"on a<div>. Now screen readers say “button,” but it’s still not keyboard-focusable and Enter/Space don’t activate it. You’d need to addtabindex="0"and keyboard handlers too. Just use<button>. -
Color contrast for placeholder text. Placeholders default to light gray, which often fails contrast. If you’re using them despite the “use labels” rule, ensure 4.5:1.
-
Auto-playing video/audio. Disorienting at best, hostile at worst. Don’t auto-play with sound.
-
Carousels and sliders. Hard to make accessible. Often unnecessary. Reconsider.
-
Modals that don’t trap focus. When a modal opens, focus should move INTO it; Tab should cycle within it; Escape should close it. shadcn/ui handles this.
-
Animations that ignore
prefers-reduced-motion. Some users get vertigo from animations. Respect the media query:@media (prefers-reduced-motion: reduce) { * { animation-duration: 0.01ms !important; transition-duration: 0.01ms !important; } } -
Form errors only shown by color. Pair colored borders with text error messages and an icon.
-
Inaccessible “skip to content” missing. A first-tab “Skip to main content” link lets keyboard users bypass repeated navigation. Easy to add, big help.
-
Treating “ARIA fixes everything” as the rule. Most ARIA misuse is worse than no ARIA. Semantic HTML first.
-
Testing only with Lighthouse. Lighthouse catches a fraction. Use axe + manual keyboard + manual screen reader.
-
Hidden vs
aria-hiddenvsdisplay: none.display: noneremoves from layout AND from accessibility tree.aria-hidden="true"removes from accessibility tree only.visibility: hiddenkeeps space, removes from a11y. Use the right one.
See also
- HTML 🟩 — semantic HTML is 80% of accessibility
- CSS 🟩 — color contrast, focus styles
- Responsive design 🟩 — overlaps heavily
- Tailwind CSS 🟩 — focus utilities
- ui 🟩 — built on Radix which handles a11y
- Dark mode 🟩 — contrast considerations
- Accessibility — design view 🟥
- Glossary: Accessibility (a11y), ARIA, WCAG
Sources
- WCAG 2.1 (W3C)
- WebAIM — articles — practical, beginner-friendly
- MDN — Accessibility
- The A11Y Project — checklist
- Inclusive Components (Heydon Pickering) — component-by-component a11y deep-dives
- WebAIM Contrast Checker
- Anthropic Constitutional AI — not strictly a11y but relevant — modern AI tools should be inclusive too