Start with semantic structure
Use one main landmark and a descriptive page heading. Use links to navigate and buttons to perform actions. A clickable div needs keyboard behavior that a native button already provides. Keep heading levels in order so readers can navigate the document by its outline.
<a class="skip-link" href="#main">Skip to content</a>
<header><nav aria-label="Primary"><a href="/components">Components</a></nav></header>
<main id="main" tabindex="-1">
<h1>Your component library</h1>
<button type="button">Save component</button>
</main>
Keep the skip link visible on focus and ensure it moves focus to the main content. Label multiple navigation landmarks distinctly. Give meaningful images useful alternative text; use an empty alt value for decorative images.
Give forms labels and recoverable errors
A placeholder does not replace a label. Associate helper text and errors with the control, identify the invalid state, and preserve entered data after a failed request.
<label for="email">Email address</label>
<input id="email" type="email" autocomplete="email" required
aria-describedby="email-help email-error" aria-invalid="true">
<p id="email-help">Used for replies about your report.</p>
<p id="email-error" role="alert">Enter an email such as [email protected].</p>
Only display the error and aria-invalid state after validation fails. On a failed submission, focus the first invalid control or a linked error summary. Explain how to correct the value. A successful asynchronous action can use a polite status region; repeated loading messages should not interrupt reading.
Make keyboard focus visible
Every action must work with a keyboard. Follow the DOM order and avoid positive tabindex values. Do not remove focus outlines without an equally visible replacement.
:where(a, button, input, select, textarea):focus-visible {
outline: 3px solid #1646c9;
outline-offset: 4px;
}
@media (prefers-reduced-motion: reduce) {
.animated-preview { animation: none; transition: none; }
}
For dialogs, move focus inside when they open, contain focus, support Escape for dismissal, and restore focus to the opener. Use a native dialog or a tested dialog primitive. For tabs, implement the selected state, panel association, and arrow-key behavior together; adding a tab role alone does not implement the interaction.
Check colors and reflow
Normal text should reach at least 4.5:1 contrast; qualifying large text needs 3:1. Essential controls and focus indicators need adequate contrast too. Measure the actual foreground and composited background in both appearances, including hover, disabled, and error states. Keep a text or icon explanation beside a colored status.
At 320 CSS pixels and 200% zoom, text and controls should reflow without clipped actions. Wide source code and data tables may use a labeled, keyboard-focusable scroll region. Use generous touch targets and spacing so adjacent actions can be selected reliably.
Verify a complete task
- Navigate from the page title to search, filters, an item, and its source using only Tab, Shift+Tab, Enter, Space, and the relevant arrow keys.
- Open and close a dialog, check focus restoration, and repeat after a validation error.
- Read the flow with a desktop screen reader and a phone screen reader. Confirm names, roles, selected states, and status announcements.
- Test light and dark appearances, reduced motion, high contrast, long text, and narrow viewports.
- Run an automated accessibility scan, then manually check interaction and comprehension. A clean scan cannot establish complete accessibility.
Use the W3C accessibility tutorials and ARIA Authoring Practices for pattern-specific guidance. Record which devices, browsers, assistive technology, and states you actually tested. A palette check or a single example does not certify the complete application.