Frontend Design
Intro
Good frontend design starts with semantic HTML, small
single-responsibility components, and WCAG 2.2 AA accessibility, then
layers on performance, state management, and rendering strategy as the
app grows. Apply these conventions when designing or reviewing any
frontend application.
Overview
Component architecture
Build a tree of small, single-responsibility components. Separate
container (data/logic) from presentational (UI) components, and
co-locate related files (Button/Button.tsx, Button.test.tsx,
Button.module.css). Prefer composition — children and slots — over
deep prop configuration. Name components by what they are
(UserAvatar), not where they live (SidebarIcon).
Semantic HTML first
Start with the correct HTML element before reaching for ARIA:
<nav>, <main>, <aside>, <header>, <footer> for landmarks.
<button> for actions, <a> for navigation — never
<div onClick>.
<ul> / <ol> for lists, <table> for tabular data.
<h1> through <h6> in logical order, no skipping levels.
<dialog> for modals, <details> for disclosure widgets.
Every <img> needs alt (use alt="" for decorative images). Every
form input needs a visible <label> with matching for / id. Add
ARIA only when HTML semantics are insufficient.
Accessibility (WCAG 2.2 AA)
- Keyboard navigation: all interactive elements reachable and
operable via keyboard.
- Focus management: visible indicator, logical tab order, no focus
traps.
- Color contrast: 4.5:1 normal text, 3:1 large text and UI
components.
- Target size: at least 24x24 CSS pixels (WCAG 2.5.8).
- Motion: respect
prefers-reduced-motion; no auto-playing
animations over 5 seconds.
- Error handling: identify errors clearly, suggest corrections,
prevent data loss.
- Screen readers:
aria-live for dynamic updates, aria-label for
icon buttons.
- Authentication: no cognitive-function tests (WCAG 3.3.8).
See Level 3 for the full WCAG 2.2 AA checklist.
State management
Pick the simplest approach that fits the complexity:
| Complexity |
Solution |
| Local UI state |
useState, useReducer |
| Shared across few |
Lift state up, prop drilling (if shallow) |
| Shared across many |
React Context (low-frequency updates) |
| Complex global |
Zustand (simple API), Jotai (atomic), Redux (large teams) |
| Server state |
TanStack Query / SWR (caching, revalidation, dedup) |
| URL state |
Search params via router (useSearchParams) |
| Form state |
React Hook Form or Conform (validation, performance) |
Avoid putting everything in global state — most state is local.
Server state is not client state; use a dedicated data-fetching
library. Derive computed values instead of storing them separately.
React / Next.js patterns
Server Components (default in App Router) fetch data on the server
and ship zero JS. Add "use client" only when you need interactivity,
hooks, or browser APIs. Use <Suspense> boundaries to stream UI
progressively. Fetch data in Server Components and use TanStack Query
for client mutations. Pick a rendering strategy per route: SSG for
static, SSR for personalized, ISR for hybrid. Export metadata or
generateMetadata for SEO on each route.
Performance (Core Web Vitals)
Optimize to the three Core Web Vitals thresholds:
- LCP (< 2.5s): preload hero images, use
next/image with
priority, minimize render-blocking CSS/JS, inline critical CSS,
lean on Server Components to cut client JS.
- INP (< 200ms): avoid long tasks on the main thread, break work
with
startTransition, debounce expensive handlers, virtualize
long lists, use useOptimistic for instant feedback.
- CLS (< 0.1): set explicit
width / height on media, reserve
space for dynamic content with skeletons, never inject content
above existing content.
Analyze bundles with @next/bundle-analyzer, lazy-load heavy
components, and load fonts via next/font for zero layout shift.
Styling approaches
| Approach |
Best for |
Trade-offs |
| Tailwind CSS |
Rapid development, consistency |
Long class strings, learning curve |
| CSS Modules |
Scoped styles, small bundles |
More files, less dynamic |
| Vanilla CSS |
Simple projects, standards |
No scoping, manual organization |
| CSS-in-JS |
Dynamic styles, co-location |
Runtime cost, SSR complexity |
Prefer Tailwind or CSS Modules for new projects (zero runtime cost).
Avoid runtime CSS-in-JS in Server Components. Use CSS custom
properties for theming and keep design tokens in one place.
Project structure (Next.js App Router)
src/
app/ # Routes, layouts, pages
(marketing)/ # Route groups
dashboard/
page.tsx
loading.tsx
error.tsx
layout.tsx
globals.css
components/
ui/ # Shared primitives (Button, Input, Card)
features/ # Feature-specific components
lib/ # Utilities, helpers, constants
hooks/ # Custom React hooks
types/ # Shared TypeScript types
Group by feature for large apps, by type for small ones. Keep
components/ui/ framework-agnostic where possible, and export barrel
files (index.ts) only for public component APIs.
Gotchas
Agent-specific failure modes — provider-neutral pause-and-self-check items:
- Using
<div onClick> instead of <button> for interactive elements. A <div> is not keyboard-focusable, has no implicit ARIA role, and does not fire click events on Enter/Space. Keyboard users and assistive technology users cannot interact with it. Use the correct semantic HTML element first.
- Putting all state in a global store. Global stores cause unrelated components to re-render on every update and make the data flow hard to trace. Most state is local; lift it up only when genuinely shared; use a dedicated data-fetching library for server state.
- Adding
"use client" to a Next.js component to "make it work" without understanding why. Every "use client" boundary ships JavaScript to the browser. Adding it to a parent makes every descendant a client component. Diagnose why the component needs interactivity and push the boundary as far down the tree as possible.
- Hardcoding colors, font sizes, or spacing values in components. Hardcoded values bypass the design token system and make theming, dark mode, and accessibility changes require a search-and-replace across the codebase. Define tokens in one place and reference them everywhere.
- Omitting
alt on images or using generic text like "image". An <img alt="image"> is meaningless to a screen reader user. Decorative images need alt="" to be skipped; meaningful images need descriptive text that conveys their content.
- No
aria-live for dynamic updates. When content changes without a page reload (search results, error messages, toast notifications), assistive technology does not announce the change unless a live region is used. Wrap dynamic status updates in an aria-live="polite" region.
- Optimizing Core Web Vitals last. LCP, INP, and CLS regressions are cheapest to fix when the component is first written. Setting explicit
width/height on images, using priority on above-fold images, and avoiding long main-thread tasks are design decisions, not afterthoughts.
Full reference
WCAG 2.2 AA checklist
Criteria marked (A) are Level A; (AA) are Level AA. Both are required
for AA conformance.
Perceivable — Text alternatives:
- 1.1.1 Non-text Content (A) — all images, icons, and buttons have
descriptive
alt; decorative images use alt="" or CSS.
Perceivable — Time-based media:
- 1.2.1 Audio/Video Only (A) — transcripts for audio-only,
transcripts or audio descriptions for video-only.
- 1.2.2 Captions Prerecorded (A) — synchronized captions on
prerecorded video.
- 1.2.3 Audio Description (A) — audio description or descriptive
transcript for video.
- 1.2.4 Captions Live (AA) — real-time captions for live audio.
Perceivable — Adaptable:
- 1.3.1 Info and Relationships (A) — semantic HTML for headings,
landmarks, lists, table headers; do not rely on visual styling.
- 1.3.2 Meaningful Sequence (A) — DOM order matches visual reading
order.
- 1.3.3 Sensory Characteristics (A) — instructions do not rely solely
on shape, color, size, or location.
- 1.3.4 Orientation (AA) — content works in portrait and landscape.
- 1.3.5 Identify Input Purpose (AA) — form fields use appropriate
autocomplete attributes.
Perceivable — Distinguishable:
- 1.4.1 Use of Color (A) — color is not the only cue.
- 1.4.2 Audio Control (A) — auto-playing audio over 3s can be paused
or adjusted.
- 1.4.3 Contrast Minimum (AA) — 4.5:1 normal text, 3:1 large text
(18pt+ or 14pt+ bold).
- 1.4.4 Resize Text (AA) — readable and functional at 200% zoom.
- 1.4.5 Images of Text (AA) — use real text, not images of text.
- 1.4.10 Reflow (AA) — no horizontal scrolling at 320px viewport
(1280px at 400% zoom).
- 1.4.11 Non-text Contrast (AA) — 3:1 for UI components and graphical
objects.
- 1.4.12 Text Spacing (AA) — no content loss with overridden line
height (1.5x), paragraph spacing (2x), letter spacing (0.12em),
word spacing (0.16em).
- 1.4.13 Content on Hover/Focus (AA) — tooltips dismissible,
hoverable, persistent until dismissed.
Operable — Keyboard:
- 2.1.1 Keyboard (A) — all functionality keyboard-accessible.
- 2.1.2 No Keyboard Trap (A) — focus can always move away.
Operable — Enough time & seizures:
- 2.2.1 Timing Adjustable (A) — time limits can be turned off or
extended.
- 2.2.2 Pause, Stop, Hide (A) — auto-moving content over 5s can be
paused.
- 2.3.1 Three Flashes (A) — no more than 3 flashes per second.
Operable — Navigable:
- 2.4.1 Bypass Blocks (A) — "skip to main content" link.
- 2.4.2 Page Titled (A) — descriptive
<title>.
- 2.4.3 Focus Order (A) — logical tab order.
- 2.4.4 Link Purpose (A) — link text describes the destination.
- 2.4.5 Multiple Ways (AA) — at least two ways to find pages.
- 2.4.6 Headings and Labels (AA) — descriptive headings and labels.
- 2.4.7 Focus Visible (AA) — clearly visible focus indicator.
- 2.4.11 Focus Not Obscured (AA) — focused elements not hidden by
sticky UI.
Operable — Input modalities:
- 2.5.1 Pointer Gestures (A) — multi-finger/path gestures have
single-pointer alternatives.
- 2.5.2 Pointer Cancellation (A) — actions fire on pointer up and can
be aborted.
- 2.5.3 Label in Name (A) — visible label text is in the accessible
name.
- 2.5.4 Motion Actuation (A) — shake/tilt has button alternatives.
- 2.5.7 Dragging Movements (AA) — drag-and-drop has a non-drag
alternative.
- 2.5.8 Target Size Minimum (AA) — interactive targets at least
24x24 CSS pixels.
Understandable:
- 3.1.1 Language of Page (A) —
<html lang="...">.
- 3.1.2 Language of Parts (AA) —
lang attribute on foreign content.
- 3.2.1 On Focus (A) — focus does not trigger unexpected changes.
- 3.2.2 On Input (A) — input changes do not cause unexpected
navigation.
- 3.2.3 Consistent Navigation (AA) — navigation order consistent
across pages.
- 3.2.4 Consistent Identification (AA) — same functionality uses same
labels.
- 3.3.1 Error Identification (A) — errors clearly described in text.
- 3.3.2 Labels or Instructions (A) — visible labels or instructions.
- 3.3.3 Error Suggestion (AA) — suggest how to fix errors.
- 3.3.4 Error Prevention (AA) — reversible, verified, or confirmed
for legal/financial data.
- 3.3.7 Redundant Entry (A) — previously entered info auto-populated.
- 3.3.8 Accessible Authentication (AA) — no cognitive tests, allow
paste, support password managers.
Robust:
- 4.1.2 Name, Role, Value (A) — custom components expose name, role,
state via ARIA; prefer native HTML.
- 4.1.3 Status Messages (AA) — dynamic status announced via
aria-live or role="alert".
ARIA patterns
<!-- Icon button -->
<button aria-label="Close dialog">
<svg aria-hidden="true">...</svg>
</button>
<!-- Live region for dynamic updates -->
<div aria-live="polite" aria-atomic="true">
3 items in cart
</div>
<!-- Form with error -->
<label for="email">Email</label>
<input id="email" type="email" aria-invalid="true" aria-describedby="email-error">
<p id="email-error" role="alert">Please enter a valid email address.</p>
<!-- Modal dialog -->
<dialog aria-labelledby="dialog-title">
<h2 id="dialog-title">Confirm deletion</h2>
...
</dialog>
Base HTML skeleton:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Descriptive Page Title</title>
</head>
<body>
<a href="#main" class="sr-only focus:not-sr-only">Skip to main content</a>
<header><nav aria-label="Main">...</nav></header>
<main id="main">...</main>
<footer>...</footer>
</body>
</html>
Testing tools
- axe DevTools — browser extension for automated checks.
- Lighthouse — accessibility audit in Chrome DevTools.
- NVDA (Windows) / VoiceOver (macOS) — screen reader testing.
- Keyboard-only navigation — unplug the mouse, test every flow.
- Colour Contrast Analyser — verify contrast ratios.
- WAVE — web accessibility evaluation tool.
Further reading
1---2name: frontend-design-23description: Frontend architecture and UI design — component hierarchies, accessibility, performance, state management. Use when designing frontend architecture, building component hierarchies, making something accessible, optimizing Core Web Vitals, or choosing a state management approach.4---56# Frontend Design78## Intro910Good frontend design starts with semantic HTML, small11single-responsibility components, and WCAG 2.2 AA accessibility, then12layers on performance, state management, and rendering strategy as the13app grows. Apply these conventions when designing or reviewing any14frontend application.1516## Overview1718### Component architecture1920Build a tree of small, single-responsibility components. Separate21container (data/logic) from presentational (UI) components, and22co-locate related files (`Button/Button.tsx`, `Button.test.tsx`,23`Button.module.css`). Prefer composition — children and slots — over24deep prop configuration. Name components by what they are25(`UserAvatar`), not where they live (`SidebarIcon`).2627### Semantic HTML first2829Start with the correct HTML element before reaching for ARIA:3031- `<nav>`, `<main>`, `<aside>`, `<header>`, `<footer>` for landmarks.32- `<button>` for actions, `<a>` for navigation — never33 `<div onClick>`.34- `<ul>` / `<ol>` for lists, `<table>` for tabular data.35- `<h1>` through `<h6>` in logical order, no skipping levels.36- `<dialog>` for modals, `<details>` for disclosure widgets.3738Every `<img>` needs `alt` (use `alt=""` for decorative images). Every39form input needs a visible `<label>` with matching `for` / `id`. Add40ARIA only when HTML semantics are insufficient.4142### Accessibility (WCAG 2.2 AA)4344- Keyboard navigation: all interactive elements reachable and45 operable via keyboard.46- Focus management: visible indicator, logical tab order, no focus47 traps.48- Color contrast: 4.5:1 normal text, 3:1 large text and UI49 components.50- Target size: at least 24x24 CSS pixels (WCAG 2.5.8).51- Motion: respect `prefers-reduced-motion`; no auto-playing52 animations over 5 seconds.53- Error handling: identify errors clearly, suggest corrections,54 prevent data loss.55- Screen readers: `aria-live` for dynamic updates, `aria-label` for56 icon buttons.57- Authentication: no cognitive-function tests (WCAG 3.3.8).5859See Level 3 for the full WCAG 2.2 AA checklist.6061### State management6263Pick the simplest approach that fits the complexity:6465| Complexity | Solution |66|-------------------|--------------------------------------------------------|67| Local UI state | `useState`, `useReducer` |68| Shared across few | Lift state up, prop drilling (if shallow) |69| Shared across many| React Context (low-frequency updates) |70| Complex global | Zustand (simple API), Jotai (atomic), Redux (large teams) |71| Server state | TanStack Query / SWR (caching, revalidation, dedup) |72| URL state | Search params via router (`useSearchParams`) |73| Form state | React Hook Form or Conform (validation, performance) |7475Avoid putting everything in global state — most state is local.76Server state is not client state; use a dedicated data-fetching77library. Derive computed values instead of storing them separately.7879### React / Next.js patterns8081Server Components (default in App Router) fetch data on the server82and ship zero JS. Add `"use client"` only when you need interactivity,83hooks, or browser APIs. Use `<Suspense>` boundaries to stream UI84progressively. Fetch data in Server Components and use TanStack Query85for client mutations. Pick a rendering strategy per route: SSG for86static, SSR for personalized, ISR for hybrid. Export `metadata` or87`generateMetadata` for SEO on each route.8889### Performance (Core Web Vitals)9091Optimize to the three Core Web Vitals thresholds:9293- **LCP (< 2.5s):** preload hero images, use `next/image` with94 `priority`, minimize render-blocking CSS/JS, inline critical CSS,95 lean on Server Components to cut client JS.96- **INP (< 200ms):** avoid long tasks on the main thread, break work97 with `startTransition`, debounce expensive handlers, virtualize98 long lists, use `useOptimistic` for instant feedback.99- **CLS (< 0.1):** set explicit `width` / `height` on media, reserve100 space for dynamic content with skeletons, never inject content101 above existing content.102103Analyze bundles with `@next/bundle-analyzer`, lazy-load heavy104components, and load fonts via `next/font` for zero layout shift.105106### Styling approaches107108| Approach | Best for | Trade-offs |109|----------------|---------------------------------|----------------------------------|110| Tailwind CSS | Rapid development, consistency | Long class strings, learning curve |111| CSS Modules | Scoped styles, small bundles | More files, less dynamic |112| Vanilla CSS | Simple projects, standards | No scoping, manual organization |113| CSS-in-JS | Dynamic styles, co-location | Runtime cost, SSR complexity |114115Prefer Tailwind or CSS Modules for new projects (zero runtime cost).116Avoid runtime CSS-in-JS in Server Components. Use CSS custom117properties for theming and keep design tokens in one place.118119### Project structure (Next.js App Router)120121```122src/123 app/ # Routes, layouts, pages124 (marketing)/ # Route groups125 dashboard/126 page.tsx127 loading.tsx128 error.tsx129 layout.tsx130 globals.css131 components/132 ui/ # Shared primitives (Button, Input, Card)133 features/ # Feature-specific components134 lib/ # Utilities, helpers, constants135 hooks/ # Custom React hooks136 types/ # Shared TypeScript types137```138139Group by feature for large apps, by type for small ones. Keep140`components/ui/` framework-agnostic where possible, and export barrel141files (`index.ts`) only for public component APIs.142143## Gotchas144145Agent-specific failure modes — provider-neutral pause-and-self-check items:146147- **Using `<div onClick>` instead of `<button>` for interactive elements.** A `<div>` is not keyboard-focusable, has no implicit ARIA role, and does not fire click events on Enter/Space. Keyboard users and assistive technology users cannot interact with it. Use the correct semantic HTML element first.148- **Putting all state in a global store.** Global stores cause unrelated components to re-render on every update and make the data flow hard to trace. Most state is local; lift it up only when genuinely shared; use a dedicated data-fetching library for server state.149- **Adding `"use client"` to a Next.js component to "make it work" without understanding why.** Every `"use client"` boundary ships JavaScript to the browser. Adding it to a parent makes every descendant a client component. Diagnose why the component needs interactivity and push the boundary as far down the tree as possible.150- **Hardcoding colors, font sizes, or spacing values in components.** Hardcoded values bypass the design token system and make theming, dark mode, and accessibility changes require a search-and-replace across the codebase. Define tokens in one place and reference them everywhere.151- **Omitting `alt` on images or using generic text like "image".** An `<img alt="image">` is meaningless to a screen reader user. Decorative images need `alt=""` to be skipped; meaningful images need descriptive text that conveys their content.152- **No `aria-live` for dynamic updates.** When content changes without a page reload (search results, error messages, toast notifications), assistive technology does not announce the change unless a live region is used. Wrap dynamic status updates in an `aria-live="polite"` region.153- **Optimizing Core Web Vitals last.** LCP, INP, and CLS regressions are cheapest to fix when the component is first written. Setting explicit `width`/`height` on images, using `priority` on above-fold images, and avoiding long main-thread tasks are design decisions, not afterthoughts.154155## Full reference156157### WCAG 2.2 AA checklist158159Criteria marked (A) are Level A; (AA) are Level AA. Both are required160for AA conformance.161162**Perceivable — Text alternatives:**163164- 1.1.1 Non-text Content (A) — all images, icons, and buttons have165 descriptive `alt`; decorative images use `alt=""` or CSS.166167**Perceivable — Time-based media:**168169- 1.2.1 Audio/Video Only (A) — transcripts for audio-only,170 transcripts or audio descriptions for video-only.171- 1.2.2 Captions Prerecorded (A) — synchronized captions on172 prerecorded video.173- 1.2.3 Audio Description (A) — audio description or descriptive174 transcript for video.175- 1.2.4 Captions Live (AA) — real-time captions for live audio.176177**Perceivable — Adaptable:**178179- 1.3.1 Info and Relationships (A) — semantic HTML for headings,180 landmarks, lists, table headers; do not rely on visual styling.181- 1.3.2 Meaningful Sequence (A) — DOM order matches visual reading182 order.183- 1.3.3 Sensory Characteristics (A) — instructions do not rely solely184 on shape, color, size, or location.185- 1.3.4 Orientation (AA) — content works in portrait and landscape.186- 1.3.5 Identify Input Purpose (AA) — form fields use appropriate187 `autocomplete` attributes.188189**Perceivable — Distinguishable:**190191- 1.4.1 Use of Color (A) — color is not the only cue.192- 1.4.2 Audio Control (A) — auto-playing audio over 3s can be paused193 or adjusted.194- 1.4.3 Contrast Minimum (AA) — 4.5:1 normal text, 3:1 large text195 (18pt+ or 14pt+ bold).196- 1.4.4 Resize Text (AA) — readable and functional at 200% zoom.197- 1.4.5 Images of Text (AA) — use real text, not images of text.198- 1.4.10 Reflow (AA) — no horizontal scrolling at 320px viewport199 (1280px at 400% zoom).200- 1.4.11 Non-text Contrast (AA) — 3:1 for UI components and graphical201 objects.202- 1.4.12 Text Spacing (AA) — no content loss with overridden line203 height (1.5x), paragraph spacing (2x), letter spacing (0.12em),204 word spacing (0.16em).205- 1.4.13 Content on Hover/Focus (AA) — tooltips dismissible,206 hoverable, persistent until dismissed.207208**Operable — Keyboard:**209210- 2.1.1 Keyboard (A) — all functionality keyboard-accessible.211- 2.1.2 No Keyboard Trap (A) — focus can always move away.212213**Operable — Enough time & seizures:**214215- 2.2.1 Timing Adjustable (A) — time limits can be turned off or216 extended.217- 2.2.2 Pause, Stop, Hide (A) — auto-moving content over 5s can be218 paused.219- 2.3.1 Three Flashes (A) — no more than 3 flashes per second.220221**Operable — Navigable:**222223- 2.4.1 Bypass Blocks (A) — "skip to main content" link.224- 2.4.2 Page Titled (A) — descriptive `<title>`.225- 2.4.3 Focus Order (A) — logical tab order.226- 2.4.4 Link Purpose (A) — link text describes the destination.227- 2.4.5 Multiple Ways (AA) — at least two ways to find pages.228- 2.4.6 Headings and Labels (AA) — descriptive headings and labels.229- 2.4.7 Focus Visible (AA) — clearly visible focus indicator.230- 2.4.11 Focus Not Obscured (AA) — focused elements not hidden by231 sticky UI.232233**Operable — Input modalities:**234235- 2.5.1 Pointer Gestures (A) — multi-finger/path gestures have236 single-pointer alternatives.237- 2.5.2 Pointer Cancellation (A) — actions fire on pointer up and can238 be aborted.239- 2.5.3 Label in Name (A) — visible label text is in the accessible240 name.241- 2.5.4 Motion Actuation (A) — shake/tilt has button alternatives.242- 2.5.7 Dragging Movements (AA) — drag-and-drop has a non-drag243 alternative.244- 2.5.8 Target Size Minimum (AA) — interactive targets at least245 24x24 CSS pixels.246247**Understandable:**248249- 3.1.1 Language of Page (A) — `<html lang="...">`.250- 3.1.2 Language of Parts (AA) — `lang` attribute on foreign content.251- 3.2.1 On Focus (A) — focus does not trigger unexpected changes.252- 3.2.2 On Input (A) — input changes do not cause unexpected253 navigation.254- 3.2.3 Consistent Navigation (AA) — navigation order consistent255 across pages.256- 3.2.4 Consistent Identification (AA) — same functionality uses same257 labels.258- 3.3.1 Error Identification (A) — errors clearly described in text.259- 3.3.2 Labels or Instructions (A) — visible labels or instructions.260- 3.3.3 Error Suggestion (AA) — suggest how to fix errors.261- 3.3.4 Error Prevention (AA) — reversible, verified, or confirmed262 for legal/financial data.263- 3.3.7 Redundant Entry (A) — previously entered info auto-populated.264- 3.3.8 Accessible Authentication (AA) — no cognitive tests, allow265 paste, support password managers.266267**Robust:**268269- 4.1.2 Name, Role, Value (A) — custom components expose name, role,270 state via ARIA; prefer native HTML.271- 4.1.3 Status Messages (AA) — dynamic status announced via272 `aria-live` or `role="alert"`.273274### ARIA patterns275276```html277<!-- Icon button -->278<button aria-label="Close dialog">279 <svg aria-hidden="true">...</svg>280</button>281282<!-- Live region for dynamic updates -->283<div aria-live="polite" aria-atomic="true">284 3 items in cart285</div>286287<!-- Form with error -->288<label for="email">Email</label>289<input id="email" type="email" aria-invalid="true" aria-describedby="email-error">290<p id="email-error" role="alert">Please enter a valid email address.</p>291292<!-- Modal dialog -->293<dialog aria-labelledby="dialog-title">294 <h2 id="dialog-title">Confirm deletion</h2>295 ...296</dialog>297```298299Base HTML skeleton:300301```html302<!DOCTYPE html>303<html lang="en">304<head>305 <meta charset="UTF-8">306 <meta name="viewport" content="width=device-width, initial-scale=1.0">307 <title>Descriptive Page Title</title>308</head>309<body>310 <a href="#main" class="sr-only focus:not-sr-only">Skip to main content</a>311 <header><nav aria-label="Main">...</nav></header>312 <main id="main">...</main>313 <footer>...</footer>314</body>315</html>316```317318### Testing tools319320- **axe DevTools** — browser extension for automated checks.321- **Lighthouse** — accessibility audit in Chrome DevTools.322- **NVDA** (Windows) / **VoiceOver** (macOS) — screen reader testing.323- **Keyboard-only navigation** — unplug the mouse, test every flow.324- **Colour Contrast Analyser** — verify contrast ratios.325- **WAVE** — web accessibility evaluation tool.326327### Further reading328329- `references/accessibility-checklist.md` — source for the WCAG 2.2330 AA checklist above.331- [React documentation](https://react.dev)332- [Next.js documentation](https://nextjs.org/docs)333- [Web Content Accessibility Guidelines 2.2](https://www.w3.org/TR/WCAG22/)