Skip to main content
友田 陽大
Frontend
React
Next.js
フロントエンド
アクセシビリティ
TypeScript
アーキテクチャ設計

Web accessibility implementation guide [2026 edition] — practical techniques to comply with WCAG 2.2 in React / Next.js

A complete guide to accessibility implementation that complies with WCAG 2.2 (AA) in React / Next.js. With real code it explains semantic HTML, keyboard operation, focus management, the correct use of ARIA, forms, contrast, prefers-reduced-motion, the new WCAG 2.2 criteria, and automated testing with axe.

Published
Reading time
10 min read
Author
友田 陽大
Share

Accessibility is where the "total score of quality" appears. Sloppy component design, gaps in state management, neglected focus — all surface as a11y defects. Conversely, a team that can guarantee a11y has high code quality itself.


1. Why accessibility now (the business viewpoint)

Before the technical discussion, let's organize the value for the client.

  • Expanding the target users. People who need some support amount to a considerable fraction of the population. a11y is directly an expansion of the reachable user count.
  • Legal / procurement requirements. In public and large-enterprise procurement, WCAG compliance is increasingly a requirement. Not complying can mean "not even being a candidate."
  • Synergy with SEO. Proper heading structure, alt text, and semantic markup work for both search engines and screen readers. a11y improvement is also acquisition improvement.
  • Proof of quality. An accessible product is evidence that the design is careful. It becomes a trust material for the ordering decision.

In other words, a11y is not a "cost" but an investment in "reach, compliance, SEO, and trust."


2. The foundation: WCAG 2.2's 4 principles (POUR)

WCAG all boils down to 4 principles. Keep these in mind and you can understand individual techniques by "why they're needed."

PrincipleMeaningRepresentative example
Perceivableinformation can be perceivedimage alt text, sufficient contrast
Operablethe UI can be operatedkeyboard operation, sufficient target size
Understandablecontent and operation can be understoodconsistent navigation, clear errors
Robustassistive technology can interpretvalid HTML, proper name/role/value

The levels are A (minimum) < AA (standard) < AAA (highest). The practical goal is AA.


3. The biggest lever: fully use semantic HTML

80% of a11y can be achieved just by using the correct element. Stopping "making" buttons and links with div and span is the most cost-effective improvement.

// ❌ アンチパターン:div をボタンに見せかける
<div className="btn" onClick={handleClick}>送信</div>

// ✅ button は「フォーカス可能・Enter/Space で発火・支援技術がボタンと認識」を無料で得る
<button type="button" onClick={handleClick}>送信</button>

Key points:

  • Page transition is a (in Next.js, Link), an action is button. Choose by meaning, not appearance.
  • Keep the heading hierarchy. h1 is one per page; don't skip h2 → h3. Screen-reader users "skim-read" the page by headings.
  • Show regions with landmarks. Use header / nav / main / footer and assistive technology can present the page structure. main is one.
  • Lists are ul/ol, tables are table + th (with scope). A div that only resembles them in appearance loses structural information.

Semantic HTML is overwhelmingly more robust and lower in maintenance cost than "supplementing later with ARIA." This is KISS itself.


4. Keyboard operation and focus management

For users who can't use a mouse (motor disability, screen reader, power user), being able to reach all functions with the keyboard alone is a lifeline.

4-1. Can you reach every operation with the keyboard

  • Move in a logical order with Tab, and operate with Enter/Space/arrows.
  • tabindex is only 0 (include in focus order) or -1 (programmatically focusable). A positive tabindex breaks the focus order, so don't use it.
  • Follow the keyboard patterns of the WAI-ARIA Authoring Practices for custom widgets (tabs, menus, comboboxes). Not building them yourself but using a verified headless UI like Radix UI is safe and fast.

4-2. Focus must be "visible"

/* ❌ 絶対にやってはいけない:フォーカスリングを消すだけ */
:focus { outline: none; }

/* ✅ マウス利用時は控えめ、キーボード利用時ははっきり見せる */
:focus-visible {
  outline: 2px solid currentColor;
  outline-offset: 2px;
}

4-3. Focus movement on route transition / modal

In an SPA / App Router, focus tends to be left behind even when the page transitions. For a modal, "focus inside on open, return to the opening element on close, and not touchable behind with Tab (focus trap)" is essential.

function Dialog({ open, onClose, children }: DialogProps) {
  const ref = useRef<HTMLDivElement>(null);
  const opener = useRef<HTMLElement | null>(null);

  useEffect(() => {
    if (open) {
      opener.current = document.activeElement as HTMLElement;
      ref.current?.focus(); // 開いたら中へ
    } else {
      opener.current?.focus(); // 閉じたら開いた要素へ戻す
    }
  }, [open]);

  // Escape で閉じる、フォーカストラップ等は Radix Dialog 等に任せるのが堅実
  return open ? (
    <div role="dialog" aria-modal="true" tabIndex={-1} ref={ref}>
      {children}
    </div>
  ) : null;
}

In practice, using Radix UI's Dialog, which has such behavior complete, is the shortest and safest (this site's UI is also Radix-based). Reinventing the wheel tends to become a YAGNI violation.


5. ARIA is "the last resort" — the correct way to use it

ARIA is a tool to supplement dynamic information that HTML alone can't express (expanded state, live updates, etc.). Overuse is counterproductive.

ARIA's 5 iron rules (a summary of the official "Rules of ARIA"):

  1. If there's a native HTML element, use it. button over role="button".
  2. Don't override a native meaning with ARIA. Don't destroy like <h2 role="tab">.
  3. All interactive ARIA must be keyboard-operable.
  4. Don't hide a focusable element with role="presentation" / aria-hidden="true". (Don't create the contradiction of being focusable yet invisible to assistive technology)
  5. Give an accessible name to all operable elements.

Commonly-used correct examples:

// アイコンのみボタンには必ずラベルを
<button aria-label="メニューを開く" onClick={toggle}>
  <MenuIcon aria-hidden="true" />
</button>

// 開閉状態を伝える
<button aria-expanded={open} aria-controls="panel">詳細</button>
<div id="panel" hidden={!open}>...</div>

// 動的更新の通知(バリデーション結果やトースト)
<div role="status" aria-live="polite">{message}</div>

6. Form accessibility

Forms are where a11y failures are most common (for detailed implementation, see the React Hook Form × Zod practical guide). Keep at least these.

  • A label for every input (associated with htmlFor). A placeholder is not a substitute for a label (it disappears on input and has low contrast).
  • Tie errors to the input with aria-invalid and aria-describedby, and notify with role="alert".
  • Don't rely on color alone for required. Show with text or * + a legend, and add aria-required.
  • On submit failure, focus the first error item.
<label htmlFor="email">メールアドレス</label>
<input
  id="email"
  type="email"
  aria-invalid={!!error}
  aria-describedby={error ? "email-error" : undefined}
/>
{error && <p id="email-error" role="alert">{error}</p>}

7. Color, contrast, motion

Color and contrast

  • Body text has a contrast ratio of 4.5:1 or more with the background (large text is 3:1). It's an AA mandatory criterion.
  • Don't convey information by color alone. "Red = error" is used together with an icon or text (consideration for color-vision characteristics).

Motion (prefers-reduced-motion)

For people with vestibular disorders, large animations cause physical discomfort. Respect the OS's "reduce motion" setting. This site's animation layer is also designed to respect prefers-reduced-motion.

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

You can also judge it on the React side.

const reduced = useReducedMotion(); // motion ライブラリのフック等
<motion.div animate={reduced ? {} : { y: [20, 0], opacity: [0, 1] }} />

8. New WCAG 2.2 criteria (the diff to grasp in 2026)

Of the 9 criteria added in WCAG 2.2, here's an implementation-viewpoint summary of the main ones affecting AA.

CriterionLevelWhat to do in implementation
2.4.11 Focus Not Obscured (Minimum)AAa fixed header or cookie banner doesn't completely hide the focused element
2.5.7 Dragging MovementsAAprovide an alternative means such as a click for operations done by drag
2.5.8 Target Size (Minimum)AAclick targets are in principle 24×24px or more, or ensure sufficient spacing
3.2.6 Consistent HelpAplace help paths like inquiries in the same position/order across pages
3.3.7 Redundant EntryAdon't make the user re-enter the same info in the same procedure (autocomplete, carry-over)
3.3.8 Accessible Authentication (Minimum)AAdon't require password memorization or puzzles. Allow password paste and passkeys

In particular, 2.5.8 Target Size and 3.3.8 Accessible Authentication are often overlooked on existing sites. A cluster of small icon buttons, a paste-forbidden password field, and a CAPTCHA-required login all become problems in 2.2.


9. Testing strategy: a two-tier setup of automated + manual

Automated inspection alone can detect only 30–50% of a11y problems. Protecting the foundation automatically and confirming the experience manually is the right answer.

Automated: build axe into CI

// tests/e2e/a11y.spec.ts (Playwright + @axe-core/playwright)
import { test, expect } from "@playwright/test";
import AxeBuilder from "@axe-core/playwright";

test("トップページに重大な a11y 違反がない", async ({ page }) => {
  await page.goto("/");
  const results = await new AxeBuilder({ page })
    .withTags(["wcag2a", "wcag2aa", "wcag22aa"])
    .analyze();
  expect(results.violations).toEqual([]);
});

During development, detect statically with eslint-plugin-jsx-a11y, and use jest-axe / vitest-axe per component, and you can crush problems early and cheaply (shift left).

Manual: things only a human can tell

  • All operations by keyboard alone. Pull out the mouse and go around with Tab, Enter, arrows, Escape.
  • Screen reader. Listen to the main flows with macOS VoiceOver and Windows NVDA.
  • Magnified display. Check that the layout doesn't break at 200% zoom and 400%-equivalent.

10. Anti-patterns (common failures)

  • ❌ Removing the focus ring with outline: none (keyboard users get lost).
  • ❌ Putting only onClick on div/span to make a button (focus, keyboard, role all missing).
  • ❌ Using placeholder as a label substitute.
  • ❌ Piling on meaningless role or aria-* ("ARIA for now" becomes harmful).
  • ❌ Not distinguishing decorative images that should have an empty alt from images with content (decoration is alt="", meaningful images get a description).
  • ❌ Not doing a focus trap and return on modals.
  • ❌ Ignoring prefers-reduced-motion in animations.

11. FAQ (frequently asked questions)

Q. Which level (A/AA/AAA) should I aim for? A. The practical standard is AA. Many procurements and guidelines use AA as the baseline. AAA is realistically aimed at partially, for some criteria only.

Q. Can I say I'm WCAG-compliant if I pass axe (automated inspection)? A. No. Automated inspection covers only the mechanically-judgeable range (about 30–50%). The experience with keyboard operation and screen readers needs manual confirmation.

Q. Does accessibility support help SEO? A. It does. Heading structure, alt text, semantic HTML, and clear link text are information sources both search engines and assistive technology read.

Q. Where do I start with a11y improvement of an existing site? A. The order of ① replacing with semantic HTML, ② keyboard operation and focus visualization, ③ form labels and errors, ④ contrast is cost-effective. Grasp the current state with automated inspection, then prioritize.

Q. Is a11y perfect if I use a headless UI (Radix, etc.)? A. The foundation greatly improves but it's not perfect. Labeling, contrast, content structure, and path consistency remain the app side's responsibility.


Conclusion: accessibility is the "total score of quality"

Accessibility is not a special specialty area but an accumulation of good HTML, good state management, and good focus design. That's exactly why a product with a11y guaranteed has high overall quality.

  1. Fully use semantic HTML (the biggest lever).
  2. Guarantee keyboard operation and focus management across all paths.
  3. Use ARIA as a supplement correctly (misuse is worse than none).
  4. Leave no one behind with forms, contrast, and motion.
  5. Reflect the new WCAG 2.2 criteria (target size, accessible authentication, etc.).
  6. Continuously protect with automated + manual testing.

These directly tie to expanding the reachable user count, meeting procurement requirements, improving SEO, and the trust of "carefully made."

If you need accessible design of a new product, or WCAG 2.2 (AA) support / improvement of an existing site, feel free to reach out. The case study below introduces the process of designing and implementing a business system used daily by diverse on-site users, balancing operability and quality.

友田

友田 陽大

Developer of a METI Minister's Award–winning product. With TypeScript + Python + AWS, I deliver SaaS, industry DX, and production-grade generative AI (RAG) end to end — from requirements to infrastructure and operations — single-handedly.

Got a challenge?

From design to implementation and operations — solo × generative AI

Implementation like this article's, end to end from requirements to production. Start with a free 30-minute technical consult and tell me about your situation.

Available for both project-based (contract) and advisory engagements. Start with a free 30-minute consult.

Also worth reading