Skip to main content

Accessibility as Design

Accessibility is not a feature you bolt on after launch — it's a design constraint that improves the experience for everyone. Screen reader users, keyboard navigators, and users with color vision deficiencies are the most obvious beneficiaries. But accessible design also helps users on slow connections, in bright sunlight, with broken trackpads, or using your app one-handed on a bus.

tip

Accessible design IS good design. Every technique on this page benefits all users, not just users with disabilities.

Lighthouse's Accessibility audit (powered by axe-core) is a fast automated complement to the manual checks on this page — see Performance & Core Web Vitals for how to run it in DevTools, CI, and against preview URLs.

The Overlap

This is the key insight. Every accessibility practice has a wider audience than you might expect:

Accessibility PracticeWho It Also Benefits
Color contrast ≥ 4.5:1Users in bright sunlight, cheap monitors, aging eyes
Keyboard navigationPower users, broken trackpad/mouse, terminal-oriented devs
Touch targets ≥ 44pxAll mobile users, especially one-handed or on-the-go
prefers-reduced-motionLow-end devices, motion-sensitive users, battery saving
Semantic HTMLSEO crawlers, reader mode, LLM parsing, future-proofing
Visible focus indicatorsKeyboard users, tab navigators, accessibility audits
Alt text on imagesSlow connections (image not loaded), SEO, link previews
Captions on videoNoisy environments, non-native speakers, searchability

Color & Contrast

Minimum Contrast Ratios

WCAG defines minimum contrast ratios for readable, accessible text. See Visual Design Fundamentals for the full contrast ratio table and color palette guidance. The short version:

  • 4.5:1 — normal text (below 18pt)
  • 3:1 — large text (18pt+ or 14pt+ bold) and UI components
  • 7:1 — enhanced (Level AAA)

Pure black on pure white (21:1) is technically fine but can feel harsh. Near-black (#1a1a1a) on near-white (#fafafa) is gentler and still well above the minimum.

Don't Rely on Color Alone

Never use color as the only way to convey meaning. About 8% of males have some form of color vision deficiency — red and green are the most commonly confused pair.

  • Bad: Status shown only with red/green dots.
  • Good: Red dot + ✕ icon + "Failed" text label. Green dot + ✓ icon + "Passed" text label.

Always pair color with text, icons, or patterns so the meaning is clear regardless of how colors are perceived.

Testing Contrast

  • Chrome DevTools: Inspect an element → click the color swatch in the Styles panel → the color picker shows the contrast ratio against the background.
  • Chrome DevTools: Open the Rendering tab → "Emulate vision deficiencies" to simulate protanopia, deuteranopia, tritanopia, and achromatopsia.
  • See Design Tools & Resources for standalone contrast checker tools.

Focus Management

Visible Focus Indicators

Browsers provide a default focus outline that works. Unfortunately, it's often removed with outline: none for aesthetic reasons. If you remove the default, you must replace it with a custom visible focus style — otherwise keyboard users cannot see where they are on the page.

focus.css
:focus-visible {
outline: 2px solid var(--color-primary);
outline-offset: 2px;
}

:focus-visible

:focus-visible shows the focus ring for keyboard navigation but not for mouse clicks. This is the correct balance — keyboard users see focus, mouse users don't get visual noise.

  • Use :focus-visible instead of :focus for styling in all cases.
  • Supported in all modern browsers.
  • The browser heuristic handles the decision automatically: keyboard interaction triggers it, mouse clicks do not.

Focus Order

  • tabindex="0" — adds the element to the natural tab order (follows DOM position).
  • tabindex="-1" — makes the element focusable programmatically (via JavaScript) but not reachable via the Tab key.
  • Never use positive tabindex values (tabindex="1", "2", etc.) — this overrides DOM order and creates an unpredictable, chaotic tab sequence. Fix the DOM order instead.

Focus Trapping in Modals

When a modal opens, trap focus inside it — pressing Tab should cycle through only the modal's interactive elements, not escape to the page behind it. When the modal closes, return focus to the element that triggered it.

  • Use the native <dialog> element — it has built-in focus trapping and Escape-to-close.
  • Alternatively, use the inert attribute on background content to prevent interaction with it.
  • Libraries like Radix UI and Headless UI handle focus trapping automatically.
  • shadcn/ui is built on Radix and inherits its focus management.

Touch & Click Targets

Minimum Sizes

Multiple standards converge on similar minimums:

  • Apple HIG: 44×44pt
  • Google Material Design: 48×48dp
  • WCAG 2.2 Level AA (SC 2.5.8): 24×24 CSS px minimum

Practical recommendation: aim for 44–48px. Never go below 24px.

touch-targets.css
button, a, [role="button"] {
min-height: 44px;
min-width: 44px;
padding: 0.5rem 1rem;
}

Spacing Between Targets

  • Maintain at least 8px between adjacent interactive elements.
  • This is especially critical when destructive actions sit next to safe ones — "Delete" right next to "Edit" with no gap is an accident waiting to happen.
  • Inline links within paragraphs should have enough padding to be visually and physically distinguishable as tap targets.

Motion & Animation

prefers-reduced-motion

Some users have vestibular disorders, migraines, or seizure conditions that are triggered by motion. Respect their preference with prefers-reduced-motion.

The recommended approach is to opt in to motion rather than opting out — start with no motion and only add it if the user hasn't indicated a preference:

motion.css
/* Base: no motion */
.element {
transition: none;
}

/* Only add motion if user hasn't set a reduce preference */
@media (prefers-reduced-motion: no-preference) {
.element {
transition: transform 200ms ease;
}
}

In JavaScript, check the preference with:

const prefersReducedMotion =
window.matchMedia('(prefers-reduced-motion: reduce)').matches;

What to Reduce

Not all motion is equal. The goal is removing vestibular triggers, not eliminating all visual feedback:

  • Eliminate: parallax scrolling, auto-playing carousels, large-scale page transitions, background video.
  • Keep: opacity fades, subtle color transitions, focus ring animations — these don't trigger vestibular issues.

Semantic HTML

Why Semantics Matter

Screen readers use landmark elements (<nav>, <main>, <aside>) to let users jump between sections of a page. A <button> gets keyboard activation (Enter and Space) for free — a <div> does not. <form> enables Enter-to-submit. <details> gets native toggle behavior.

Semantic HTML means less JavaScript and better accessibility at zero cost.

danger

A <div> with an onclick handler is not a button. It has no keyboard support, no focus, no ARIA role, and no screen reader announcement. Use <button> for actions and <a> for navigation — always.

Common Mistakes

Instead ofUse
<div onclick="..."><button>
<div class="header"><header>
<span class="link"><a href="...">
<div class="list"><div>Item</div></div><ul><li>Item</li></ul>
<b> for emphasis<strong>
Generic <div> wrappers<main>, <nav>, <aside>, <section>, <article>

Landmark Roles

Structure your page with landmarks so screen reader users can navigate efficiently:

  • <header> — site or section header
  • <nav> — navigation links
  • <main> — primary content (one per page)
  • <aside> — complementary content (sidebars, related links)
  • <footer> — site or section footer

Screen readers can jump directly between landmarks. Structure your page so this makes sense — if a screen reader user skips from <nav> to <main> to <footer>, they should land in logical places.


Quick Wins Checklist

The highest-impact, lowest-effort accessibility improvements. If you do nothing else, do these:

  1. Add lang attribute to <html> (e.g., <html lang="en">)
  2. Use semantic HTML elements (<button>, <a>, <nav>, <main>)
  3. Ensure all images have alt text (use alt="" for purely decorative images)
  4. Check text contrast ratios — minimum 4.5:1
  5. Add :focus-visible styles to all interactive elements
  6. Make all interactive elements keyboard-accessible
  7. Use <button> for actions, <a> for navigation
  8. Add prefers-reduced-motion support to animations
  9. Ensure touch targets are at least 44×44px
  10. Test with keyboard only — unplug your mouse for 5 minutes
note

This page covers the design side of accessibility. For comprehensive WCAG coverage, see the WCAG 2.2 Quick Reference and web.dev Learn Accessibility. For additional resources, see Web — Accessibility.


Further Reading