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."
| Principle | Meaning | Representative example |
|---|---|---|
| Perceivable | information can be perceived | image alt text, sufficient contrast |
| Operable | the UI can be operated | keyboard operation, sufficient target size |
| Understandable | content and operation can be understood | consistent navigation, clear errors |
| Robust | assistive technology can interpret | valid 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 isbutton. Choose by meaning, not appearance. - Keep the heading hierarchy.
h1is one per page; don't skiph2 → h3. Screen-reader users "skim-read" the page by headings. - Show regions with landmarks. Use
header/nav/main/footerand assistive technology can present the page structure.mainis one. - Lists are
ul/ol, tables aretable+th(withscope). Adivthat 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.
tabindexis only0(include in focus order) or-1(programmatically focusable). A positivetabindexbreaks 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"):
- If there's a native HTML element, use it.
buttonoverrole="button". - Don't override a native meaning with ARIA. Don't destroy like
<h2 role="tab">. - All interactive ARIA must be keyboard-operable.
- 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) - 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
labelfor every input (associated withhtmlFor). Aplaceholderis not a substitute for a label (it disappears on input and has low contrast). - Tie errors to the input with
aria-invalidandaria-describedby, and notify withrole="alert". - Don't rely on color alone for required. Show with text or
*+ a legend, and addaria-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.
| Criterion | Level | What to do in implementation |
|---|---|---|
| 2.4.11 Focus Not Obscured (Minimum) | AA | a fixed header or cookie banner doesn't completely hide the focused element |
| 2.5.7 Dragging Movements | AA | provide an alternative means such as a click for operations done by drag |
| 2.5.8 Target Size (Minimum) | AA | click targets are in principle 24×24px or more, or ensure sufficient spacing |
| 3.2.6 Consistent Help | A | place help paths like inquiries in the same position/order across pages |
| 3.3.7 Redundant Entry | A | don't make the user re-enter the same info in the same procedure (autocomplete, carry-over) |
| 3.3.8 Accessible Authentication (Minimum) | AA | don'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
onClickondiv/spanto make a button (focus, keyboard, role all missing). - ❌ Using
placeholderas a label substitute. - ❌ Piling on meaningless
roleoraria-*("ARIA for now" becomes harmful). - ❌ Not distinguishing decorative images that should have an empty
altfrom images with content (decoration isalt="", meaningful images get a description). - ❌ Not doing a focus trap and return on modals.
- ❌ Ignoring
prefers-reduced-motionin 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.
- Fully use semantic HTML (the biggest lever).
- Guarantee keyboard operation and focus management across all paths.
- Use ARIA as a supplement correctly (misuse is worse than none).
- Leave no one behind with forms, contrast, and motion.
- Reflect the new WCAG 2.2 criteria (target size, accessible authentication, etc.).
- 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.