How to Develop for Web Accessibility
- Last Edited April 24, 2026
- by Garenne Bigby
The standards you’re building against
The international standard is WCAG 2.2, published October 5, 2023 by the W3C. It’s organized around four principles — Perceivable, Operable, Understandable, Robust (POUR) — with testable success criteria at three conformance levels (A, AA, AAA). Level AA is the practical target for legal compliance.
The major accessibility laws all reference WCAG at a specific version and level:
- US DOJ April 2024 Title II Final Rule → WCAG 2.1 AA. Compliance deadlines (extended by the April 20, 2026 Interim Final Rule): April 26, 2027 for state/local entities serving populations of 50,000+, April 26, 2028 for smaller entities.
- US Section 508 (2017 refresh) → WCAG 2.0 AA
- EU European Accessibility Act (enforced since June 28, 2025) via EN 301 549 → WCAG 2.1 AA
- Canada Accessible Canada Act via CAN/ASC-EN 301 549 → WCAG 2.1 AA
- UK PSBAR → WCAG 2.1 AA
Targeting WCAG 2.2 AA satisfies all of the above (since 2.2 is backward-compatible with 2.1) and positions the codebase for forthcoming regulatory updates that adopt 2.2.
Identify the page language and language changes
Use the lang attribute on the <html> element to declare the page’s primary language (WCAG 3.1.1):
<html lang="en">
For sections of content in a different language, use lang on the surrounding element (WCAG 3.1.2):
<p>The French phrase is <span lang="fr">c'est la vie</span>.</p>
Screen readers use these attributes to switch pronunciation rules — without them, English-language screen readers attempt to pronounce French, German, or Japanese as if it were English, which produces unintelligible output. The cost is one HTML attribute; the benefit is correct rendering for assistive technology users.
Associate a label with every form control
Every form input needs a programmatic label (WCAG 1.3.1, 3.3.2, 4.1.2). The <label> element is the most reliable mechanism:
<label for="email">Email address</label>
<input type="email" id="email" name="email">
Wrapping the input in the label also works:
<label>Email address <input type="email" name="email"></label>
Where a visible label isn’t appropriate (icon-only buttons, search inputs in a header), use aria-label or aria-labelledby:
<input type="search" aria-label="Search products" name="q">
Placeholder attributes are not labels. Placeholders disappear when the user starts typing, contrast tends to be insufficient, and screen readers may or may not announce them. Always pair placeholders with a real label — never use placeholders as the only label.
WCAG 2.2 added 3.3.7 Redundant Entry: information the user already entered in the same session must not require re-entry. Implement this with autofill, carry-forward state, or pre-filled fields. WCAG 2.2 also added 3.3.8 Accessible Authentication: login flows must not require a cognitive function test that cannot be passed by assistive technology — support password managers, biometrics, or other alternatives.
Provide alt text for images
Every meaningful image needs descriptive alt text (WCAG 1.1.1):
<img src="filter-installation.jpg" alt="Step 3: snap the new filter into the bracket">
Decorative images use empty alt so screen readers skip them entirely:
<img src="divider.svg" alt="">
For functional images inside buttons or links, alt text describes the action, not the image:
<button>
<img src="search-icon.svg" alt="Search">
</button>
Or, equivalently, use aria-label on the button and empty alt on the icon. For complex images (charts, infographics), provide a short alt plus a longer description in surrounding text or via longdesc/link. Avoid text-as-image — text in HTML stays sharp at any zoom level; pixel text gets blurry. WCAG 1.4.5 generally prohibits images-of-text except for logos.
Help users avoid and correct mistakes
Form errors should be obvious, descriptive, and recoverable (WCAG 3.3.1, 3.3.3, 3.3.4):
- Identify the error in text near the field, not just by color or icon. “Email is required” beats turning the field border red.
- Use
aria-describedbyto link the error message to the field, so screen readers announce the error when the field is focused. - Use
aria-invalid="true"on fields with errors so assistive tech can announce the error state. - For destructive or financially-significant actions (deletion, payment, contract signing), provide confirmation, undo, or both (WCAG 3.3.4 Error Prevention, Level AA).
- Don’t auto-submit forms or auto-trigger destructive operations on a single keystroke or accidental click.
Inline validation (validating as the user types) helps when implemented gracefully but harms when it interrupts. Validate on blur, not keystroke, for cognitively-disabled users who need time to complete each field.
Match code order to reading order
Screen readers, assistive technologies, and search-engine crawlers all read content in DOM order. CSS can rearrange visual presentation (Flexbox order, Grid grid-row, absolute positioning), but those rearrangements don’t carry to assistive tech. The DOM source order must match the logical reading order users expect (WCAG 1.3.2 Meaningful Sequence).
Practical implications:
- Don’t use CSS to reorder content into an unrelated visual sequence and call it accessible.
- Form labels should appear immediately before their inputs in the DOM.
- Section headings should appear before the content they describe in the DOM.
- Modal dialogs should be DOM-adjacent to their trigger so focus management is straightforward.
- For complex layouts that need to reorder visually, consider whether the underlying content structure could be reorganized — or use an ARIA technique that explicitly handles the reordering.
Convey meaning and structure with semantic markup
Semantic HTML carries meaning that styled <div> elements don’t (WCAG 1.3.1):
- Use headings (
<h1>–<h6>) in correct hierarchy. Exactly one H1 per page; H2s for major sections; H3s for sub-sections; don’t skip levels. - Use landmark elements (
<header>,<nav>,<main>,<aside>,<footer>,<article>,<section>) so screen reader users can jump to regions directly. - Use list elements (
<ul>,<ol>,<dl>) for actual lists. Screen readers announce list length and let users navigate item-by-item. - Use data tables (
<table>) only for tabular data, with<th>for headers andscopeattributes for column/row association. Use CSS Grid or Flexbox for layout. - Use real buttons (
<button>) for actions and real links (<a href>) for navigation. A<div>with a click handler is neither — it’s not focusable, doesn’t respond to Enter/Space, and provides no role information to assistive tech.
This is the W3C’s First Rule of ARIA: don’t use ARIA when a native HTML element does the job. Native HTML semantics are stronger than ARIA, and incorrect ARIA is worse than no ARIA.
Give meaning to non-standard interactive elements
When you genuinely need a custom widget (autocomplete, modal, accordion, tab panel, tree view), use ARIA roles, states, and properties to expose the widget’s behavior to assistive tech (WCAG 4.1.2 Name, Role, Value).
The WAI-ARIA Authoring Practices Guide (APG) documents the canonical patterns for common widgets — what roles, keyboard interactions, and state management are required. Don’t invent custom patterns when the APG covers your use case.
For dynamic content updates (alerts, toasts, live regions), use aria-live with appropriate politeness:
<div role="status" aria-live="polite">Saved</div>
<div role="alert" aria-live="assertive">Connection lost</div>
WCAG 4.1.3 (Status Messages, added in WCAG 2.1) specifically requires that status changes be programmatically announced.
Avoid CAPTCHA when possible
CAPTCHAs are a major accessibility barrier. Image-based CAPTCHAs are unusable for blind users; audio CAPTCHAs are often unusable for deaf-blind users; cognitively-demanding puzzles fail for users with cognitive disabilities. WCAG 2.2 explicitly addresses this with 3.3.8 Accessible Authentication (Minimum): authentication flows must not require a cognitive function test that cannot be passed by assistive technology.
Modern alternatives that comply with 3.3.8:
- Object/honeypot fields — invisible form fields that bots fill but humans don’t. Effective against unsophisticated bots, accessible to everyone.
- Behavioral analysis — services like Cloudflare Turnstile, hCaptcha’s accessibility cookie, and Google reCAPTCHA v3 score users invisibly based on behavior. No challenge for users; they pass automatically.
- Email or SMS verification at signup, then ongoing trust based on behavior.
- Rate limiting at the server level — reduces the value of automated abuse without challenging legitimate users.
- Passkeys (WebAuthn) — biometric or security-key authentication that’s both more accessible and more secure than passwords.
If you must use a visible CAPTCHA, provide multiple modalities (audio + image + logic puzzle) so each user has at least one option that works for them.
All interactive elements must be keyboard-accessible
Keyboard access is foundational (WCAG 2.1.1, 2.1.2, 2.4.7, and the new 2.4.11 in WCAG 2.2):
- Every interactive element reachable via Tab and Shift+Tab.
- Every interactive element activatable via Enter, Space, or appropriate Arrow keys.
- No keyboard traps — users can always Tab out of any component (2.1.2).
- Visible focus indicator at all times (2.4.7). Don’t
outline: nonewithout providing an alternative focus style. - WCAG 2.2 added 2.4.11 Focus Not Obscured: the focused element must not be hidden behind sticky headers, cookie banners, or persistent overlays. If you have a sticky header, ensure focused elements scroll into view above it.
- WCAG 2.2 added 2.5.8 Target Size (Minimum): interactive targets must be at least 24×24 CSS pixels (with limited exceptions for inline links and spacing).
- Custom keyboard shortcuts can be turned off or remapped (WCAG 2.1.4).
The single highest-signal accessibility test is keyboard-only navigation. Unplug the mouse, tab through your site, and verify everything works. If you can’t complete the task with the keyboard, neither can a substantial share of your users.
Write code that adapts to the user’s technology
Robust accessibility means content works with current and future assistive technologies — not just the ones you tested with (WCAG 4.1.x, the Robust principle).
- Use semantic HTML as the foundation. Native semantics are universally understood by assistive tech.
- Use ARIA correctly when native HTML doesn’t carry the needed semantics. Test that screen readers actually announce what you intend.
- Avoid heavy client-side-only rendering for content that needs to be accessible at first paint. Server-side rendering (SSR) or static generation (SSG) ensures critical content is in the initial HTML.
- Test with multiple screen readers: NVDA (free, Windows), VoiceOver (built-in, macOS/iOS), TalkBack (built-in, Android), JAWS (commercial, Windows).
- Respect user preferences:
prefers-reduced-motionfor users who opt out of animation,prefers-color-schemefor dark mode,forced-colorsfor Windows high-contrast users.
Modern frameworks make accessible development substantially easier. Next.js (React), Nuxt (Vue), Astro, SvelteKit, and Remix all have strong accessibility primitives built in: server-side rendering by default, accessible <Link> components, focus management on route changes, and accessibility lint rules in their default ESLint configurations. Use the framework’s idiomatic patterns rather than reaching for raw onClick handlers on <div> elements.
Modern dev tooling for accessibility
Accessibility belongs in the development pipeline, not as a final-stage review:
- axe DevTools — Chrome/Firefox browser extension. Free tier covers core WCAG rules. Catches the bulk of automated-detectable issues during development.
- WAVE (WebAIM) — visual overlay marking accessibility issues directly on the rendered page. Excellent for teaching teammates what “accessibility issue” looks like.
- Lighthouse (built into Chrome DevTools) — uses axe-core for the accessibility audit. Useful for trend tracking and CI integration.
- Pa11y — open-source CLI tool. Drop into CI to catch regressions on every deploy.
- eslint-plugin-jsx-a11y (React) and similar lint plugins for other frameworks — catches accessibility issues at lint time, before code even runs.
- Storybook a11y addon — runs accessibility checks against components in isolation, useful for design-system development.
- Cypress and Playwright accessibility plugins — integrate accessibility assertions into end-to-end tests.
Run automated checks on every deploy. Combine with manual keyboard testing and periodic screen-reader testing for the issues automated tools miss (typically 50–70% of WCAG violations require human review).
Frequently asked questions
What’s the highest-impact accessibility practice for developers?
Use semantic HTML. Real <button>, <a>, <form>, <label>, <nav>, <main>, <article>, headings in proper hierarchy. The single largest source of accessibility bugs is <div>-based components masquerading as interactive elements without the underlying semantics. Get the markup right and 60–70% of accessibility issues never exist.
How much should ARIA be used?
Sparingly and correctly. The W3C’s First Rule of ARIA: don’t use ARIA when a native HTML element exists for the job. Common ARIA misuse (assigning role="button" to a div instead of using a real <button>, adding aria-required instead of required, layering custom roles on top of semantic elements) is more harmful than no ARIA at all. Reach for ARIA only for custom widgets where native HTML doesn’t carry the semantics — and follow the WAI-ARIA Authoring Practices Guide patterns.
What’s the right CAPTCHA replacement?
Behavioral analysis (Cloudflare Turnstile, hCaptcha, reCAPTCHA v3) is the most accessible mainstream option — invisible to users, no challenge required. Honeypot fields are effective against simple bots and entirely accessible. WebAuthn passkeys provide stronger authentication than passwords and meet WCAG 2.2’s Accessible Authentication criterion. Visible CAPTCHAs should be a last resort, with multiple modalities offered if used.
Can frameworks like React or Vue produce accessible sites?
Yes, but client-side-only rendering creates two problems: critical content isn’t in the initial HTML (so search-engine and AI-crawler indexing degrades), and dynamic UI updates require careful focus management to remain accessible. Use SSR or SSG for content pages (Next.js, Nuxt, Astro, Remix, SvelteKit all support this). For application UI, follow the framework’s accessibility primitives (Next.js <Link>, React Router accessibility helpers, etc.) and test with screen readers.
How do I test a custom widget for accessibility?
Three layers: (1) automated checks via axe or Lighthouse for structural issues; (2) keyboard-only navigation — verify Tab order, focus visibility, and all interactions work via keyboard; (3) screen reader testing with at least NVDA + VoiceOver — listen to how the widget is announced, verify state changes are conveyed, confirm semantics match what the WAI-ARIA APG specifies for the pattern.
Do single-page applications (SPAs) have accessibility problems?
SPAs add accessibility complexity but aren’t inherently inaccessible. Key concerns: (1) route changes don’t trigger the focus and screen-reader announcements that browser navigation does — manage focus and announce route changes manually; (2) dynamic content updates need aria-live regions; (3) initial render relies on JavaScript, which has implications for slow connections, AI crawlers, and assistive tech in some cases. Modern SPA frameworks (Next.js App Router, Remix) handle most of this automatically when used idiomatically.
Bottom line
Accessibility for developers comes down to semantic HTML, correct ARIA when needed, full keyboard support, robust focus management, and continuous tooling. Target WCAG 2.2 Level AA, lint accessibility issues at build time, run automated scans in CI, and test critical paths with keyboard and screen reader before every major release. Modern frameworks (Next.js, Nuxt, Astro, SvelteKit, Remix) make accessible development substantially easier than it was a decade ago — use their idiomatic patterns, respect user preferences via CSS media features, and keep accessibility part of the daily development practice rather than a final-stage audit. Done right, accessibility costs roughly 10–15% more during development and saves significantly more in lawsuits, lost users, and retrofit costs avoided.
Categories
- Last Edited April 24, 2026
- by Garenne Bigby