#React#TypeScript

Web Accessibility: A Practical Guide to WCAG

Accessibility is not a benefit for a minority. It supports ADA and EAA compliance, improves SEO, and raises product quality. This guide covers WCAG's four POUR principles, common frontend a11y failures, automated checks, manual testing, and accessibility gates in CI.

Key Points

  • Topic Focus: React、TypeScript
  • Suitable for: indie hackers, full-stack engineers, AI application developers
  • Core value: quickly understand background, practical paths, common pitfalls, and engineering advice
Author @admin·Published July 26, 2026·Updated July 26, 2026

Many teams treat accessibility (a11y) as a "nice-to-have" or an act of charity, forever ranking it behind feature work. That is an expensive misconception. This article explains why accessibility is essential, which standards to follow, the most common real-world pitfalls, and how tools and processes can provide safeguards.

1. a11y Is Not Charity: It Is a Compliance Baseline, an SEO Advantage, and a Measure of Product Quality

Start with compliance. In the United States, thousands of website accessibility lawsuits have been filed each year under the ADA (Americans with Disabilities Act) in recent years. Plaintiffs' law firms have turned this into an industry: they scan your site, find insufficient contrast or keyboard-inaccessible controls, send a demand letter, and negotiate settlements ranging from tens of thousands to hundreds of thousands of dollars. The EU's European Accessibility Act (EAA) took full effect in June 2025, covering digital services such as e-commerce, banking, and transportation. Violations are subject to penalties imposed by individual member states. If you build B2B products or serve international markets, there is no avoiding this requirement.

Next, SEO. A search engine crawler is essentially a "blind user": it cannot see image content (it relies on alt text), understand click behavior attached to a div (it relies on semantic elements), or operate JavaScript interactions (it relies on native HTML). The improvements you make for screen readers, including semantic structure, descriptive alt text, and a clear heading hierarchy, translate almost directly into SEO benefits. Core Web Vitals and accessibility overlap more than you might expect.

Finally, product quality. a11y forces you to ask: Can this interaction be used with a keyboard? Is the focus state clear? Is there enough color contrast? These constraints lead to more robust UIs. A page that cannot be navigated with the Tab key usually also has confused state management and brittle event handling. Treating a11y as a quality gate keeps an entire class of bugs out of production.

In short: a11y is not "charity for a minority." It sits at the intersection of legal compliance, search traffic, and engineering quality.

2. WCAG's Four Principles (POUR): The Source of Every Rule

WCAG (Web Content Accessibility Guidelines) is a W3C standard. Version 2.1 is currently mainstream, and 2.2 has been published. It groups its rules under four principles, abbreviated as POUR:

  • Perceivable: Content must be available in forms users can perceive. Blind users cannot see, so images need alt text or audio alternatives; color-blind users may not distinguish colors, so color cannot be the sole way to convey information; Deaf users cannot hear, so videos need captions.
  • Operable: The interface must be operable. Keyboard users must be able to reach every interactive element with Tab; users must not face unreasonable time pressure; flashing content must not trigger seizures.
  • Understandable: Content and interactions must be understandable. The language must be declared (<html lang>); form errors need clear messages; component behavior must be predictable.
  • Robust: Content must be parsed reliably by different user agents, including assistive technologies. HTML must be valid; ARIA must be used correctly; the interface must work with screen readers, voice control, and similar tools.

Each principle contains Success Criteria at three levels: A (minimum), AA (the mainstream target and the level commonly cited in lawsuits), and AAA (the highest level, which is not achievable in every situation). In practice, WCAG 2.1 AA is a reasonable target for almost every team. Do not start by pursuing AAA; the return on effort is extremely low.

The real value of POUR is this: when you encounter an unfamiliar situation, you can derive the answer from the four principles instead of memorizing dozens of rules. For example, "Should a loading state be announced to screen readers?" Return to Operable and Perceivable, and the answer follows naturally: yes, because otherwise the user does not know what is happening.

3. The Most Common Frontend a11y Problems and How to Fix Them

In practice, 90% of issues fall into five categories.

1. Images Lack alt Text, or Decorative Images Are Not Marked aria-hidden

tsx
// ❌ Bad: the informative image has no alt text, so the screen reader can only read the filename; the decorative image adds noise
<img src="banner.jpg" />
<img src="icon-check.png" /> <span>Saved</span>
<img src="decorator-line.svg" alt="A decorative curved line" />

// ✅ Good: describe informative images; use empty alt text and aria-hidden for decorative images
<img src="banner.jpg" alt="Key visual for the Spring 2026 product launch" />
<img src="icon-check.png" alt="" aria-hidden="true" /> <span>Saved</span>
<img src="decorator-line.svg" alt="" aria-hidden="true" />

The rule: informative image → descriptive alt text; purely decorative image → alt="" plus aria-hidden. Do not simply omit the alt attribute. That means something different, and screen readers may try to read the URL.

2. Insufficient Contrast, Especially for Gray Text and Low-Saturation Palettes

WCAG AA requires a contrast ratio of at least 4.5:1 for body text and 3:1 for large text (18pt+ or 14pt bold). The common design combination #999 on #fff has a ratio of 2.85:1 and does not pass.

tsx
// ❌ Bad: 2.85 contrast ratio, which does not pass
<p className="text-gray-400">Secondary explanatory text</p>

// ✅ Good: 7.4 contrast ratio, well above AA
<p className="text-gray-600">Secondary explanatory text</p>

This must be caught during the design phase. Run every color value in the design system through a contrast checker and reject any token that does not pass. Stark, Polypane, and Figma's A11y plugins can all do this.

3. Keyboard Inaccessibility: Tab Order, Invisible Focus, and Custom Components Without Keyboard Support

This is the most serious category because it affects every keyboard user, not only people with disabilities.

A typical example of invisible focus is globally removing outlines, which is a major accessibility violation:

tsx
// ❌ Bad
* { outline: none; }

// ✅ Good: hide the outline only for mouse clicks, while preserving it for keyboard focus
:focus:not(:focus-visible) { outline: none; }
:focus-visible { outline: 2px solid #2563eb; outline-offset: 2px; }

Custom components without keyboard support are even more common. If you simulate a button with <div onClick>, keyboard users cannot reach it with Tab, much less activate it with Enter or Space.

tsx
// ❌ Bad: a div is not a button, so it fails for keyboards and screen readers
<div onClick={handleSubmit} className="btn">Submit</div>

// ✅ Good: a native button includes Tab, Enter, Space, focus, and role behavior
<button onClick={handleSubmit} className="btn">Submit</button>

When building composite components such as Modal, Dropdown, Tabs, and Combobox, do not invent keyboard semantics yourself. Follow the patterns in the WAI-ARIA Authoring Practices Guide (APG); they have already been validated.

4. Forms Lack Labels, or Error Messages Are Not Associated with Their Fields

tsx
// ❌ Bad: the placeholder is used as a label, so screen readers cannot reliably identify the field
<input placeholder="Email" />

// ✅ Good: explicitly associate the label
<label htmlFor="email">Email</label>
<input id="email" type="email" aria-required="true" />

Error messages must also be associated with their fields; otherwise, users do not know why submission failed:

tsx
<input
  id="email"
  aria-invalid="true"
  aria-describedby="email-error"
/>
<p id="email-error" role="alert">Please enter a valid email address</p>

aria-describedby associates the error text with the input, while role="alert" causes the screen reader to announce it proactively.

5. ARIA Misuse: Arbitrary role and aria-label Attributes Can Mislead Screen Readers

The first rule of ARIA: No ARIA is better than bad ARIA. Incorrect ARIA is worse than none because it actively lies.

tsx
// ❌ Bad: adding role to a native button is redundant and may cause conflicts
<button role="button" aria-label="Button">Submit</button>

// ❌ Bad: aria-label overrides the visible text, so saying "Submit" does nothing for voice-control users
<button aria-label="Confirm submission">Submit</button>

// ❌ Bad: creates a component with ARIA but does not implement keyboard interaction
<div role="tab" tabIndex={0}>Details</div>  {/* Missing ArrowLeft/Right handling */}

Remember: ARIA does not add any behavior to an element; it only changes its semantics. If you write role="tab", a screen reader will announce "tab," but you still have to implement ArrowLeft/Right switching, the aria-selected state, and tabindex management yourself. In 90% of cases, correct semantic HTML removes the need for ARIA.

4. The First Principle: Prefer Semantic HTML; Use ARIA as a Supplement

The five problem categories above often share one root cause: using generic elements (div/span) with CSS and JavaScript to reinvent capabilities the browser already provides.

What you needDo not useUse
Button<div onClick><button>
Navigation<div class="nav"><nav>
Main content<div id="app"><main>
Heading hierarchy<div class="h1"><h1>~<h6>
Form field<div> + placeholder<input> + <label>
ListMultiple <div> elements<ul>/<ol> + <li>

Semantic HTML comes with built-in keyboard support, focus management, screen-reader announcements, and landmark navigation. Screen-reader users can jump directly to <main>, list every <h2>, or skip <nav> with a single command. All of these shortcuts depend on semantic elements. If everything is a div, you effectively destroy their navigation system.

Rule of thumb: if correct HTML can provide accessibility on its own, do not add ARIA. A native <button> is already accessible. Piling role="button" and aria-label="Button" onto it accomplishes nothing except revealing that you do not understand ARIA.

5. Automated Checks: Treat a11y as a First-Class Concern

Good intentions are not enough; tooling must provide safeguards. Use this three-part stack:

1. eslint-plugin-jsx-a11y: Static analysis that catches problems directly in the editor. Add it to ESLint, and issues such as missing alt text, onClick without onKeyDown, and misuse of tabindex can be caught while the code is being written. It has the lowest cost and delivers the fastest return.

2. @axe-core/playwright (or axe-core/webdriverjs): Runtime analysis against the actual rendered DOM. axe-core is Deque's open-source rules engine and the de facto industry standard. Integrate it into component tests or E2E tests:

ts
import { expect } from '@playwright/test';
import AxeBuilder from '@axe-core/playwright';

test('Modal has no a11y violations', async ({ page }) => {
  await page.goto('/settings');
  const results = await new AxeBuilder({ page })
    .withTags(['wcag2a', 'wcag2aa'])
    .analyze();
  expect(results.violations).toEqual([]);
});

3. Lighthouse a11y audit: This also uses axe-core internally, but adds a readable report and score. It is useful for establishing an overall baseline. @lhci/cli can track score changes, making regressions immediately visible.

The key point: automated tools can detect only about 30–40% of a11y issues, including machine-detectable problems such as insufficient contrast, missing alt text, and invalid ARIA syntax. They cannot determine whether a keyboard workflow makes sense or whether the screen-reader experience is smooth. Automated checks are therefore necessary but insufficient.

6. Test Manually, Add It to CI, and Catch It During Design

Two kinds of manual testing are unavoidable:

  1. Keyboard testing: Unplug the mouse and complete the primary workflow using only Tab / Shift+Tab / Enter / Space / arrow keys / Esc. Can you reach every interactive element? Is focus visible? Does the order match the visual sequence? When a Modal opens, does focus remain outside it? This costs nothing, takes ten minutes, and exposes 80% of operability bugs.

  2. Screen-reader testing: Use VoiceOver on Mac (turn it on with Cmd+F5) or the free, open-source NVDA on Windows. Complete the core workflow once and listen for sensible announcements. You do not need to become an expert; basic operation and an understanding of "heading list" and "landmark list" are enough.

Add it to CI: Put ESLint's a11y rules, axe-core tests for key pages, and a Lighthouse a11y score threshold into CI. Do not allow any PR that fails them to merge. A gating mechanism is more effective than any document. No matter how polished the documentation is, it cannot compete with a red X.

yaml
# Simplified .github/workflows/a11y.yml example
- run: pnpm lint                # Includes eslint-plugin-jsx-a11y
- run: pnpm test:e2e:a11y       # Key pages checked with axe-core
- run: pnpm lhci a11y --score=90   # Lighthouse a11y score must be at least 90

Consider accessibility during design: The cheapest fix happens in the design file. Color combinations that fail contrast requirements, states conveyed only through color, click targets that are too small, and component libraries without focus styles can be fixed with one change in Figma. That is 100 times cheaper than rework after release. Add "contrast ≥ 4.5:1," "every interactive element has a focus state," and "minimum target size 44×44px" to the design standards, then enforce them during design review.

Summary

  • a11y sits at the intersection of compliance, SEO, and quality; it is not charity. ADA/EAA requirements are the legal baseline, while SEO and engineering quality provide business value. Every reason leads to the same conclusion: it must be done.
  • Every rule derives from POUR's four principles: Perceivable, Operable, Understandable, and Robust. When you cannot remember a specific rule, derive it from the principles; align the entire team on WCAG 2.1 AA as the target.
  • 90% of problems fall into five categories: image alt text, contrast, keyboard accessibility, form labels, and ARIA misuse. Simulating interactive elements with divs is the root of countless problems.
  • Prefer semantic HTML; ARIA supplements it rather than replacing it. Native <button>/<nav>/<main> elements include accessibility behavior. Choose the correct element first, then consider ARIA. No ARIA is better than bad ARIA.
  • Automated checks are necessary but insufficient: Use ESLint + axe-core + Lighthouse as CI gates, then use keyboard and screen-reader testing to cover the 60–70% that tools cannot detect.

Key takeaway: accessibility is not "something you add after finishing the feature." It reflects the robustness of your page. A page that cannot be navigated with Tab is usually a poorly built page. Treat a11y as a first-class concern and enforce it throughout the process, from design to CI. You will find that it is not a burden but a competitive advantage in quality.

Related

Related Content