Accessibility
Build UIs everyone can use with semantic HTML and ARIA where needed.
Accessibility (often shortened to a11y) makes your app usable by people relying on keyboards, screen readers and other assistive tech. React renders standard HTML, so the same good practices apply.
Essentials
- Use semantic elements:
button,nav,label, headings in order. - Associate labels with inputs using
htmlFor. - Provide
alttext on images and accessible names on icon buttons. - Ensure everything works with the keyboard and add ARIA attributes only when semantics fall short.
Example
function SearchField() {
return (
<form>
<label htmlFor="q">Search</label>
<input id="q" name="q" type="search" />
<button type="submit" aria-label="Run search">
π
</button>
</form>
);
}When to use it
- A custom dropdown menu sets role='menu' and manages keyboard focus between items so screen reader users can navigate it just like a native select.
- A form with inline validation links each error message to its input via aria-describedby so screen readers announce the error when the invalid field gains focus.
- A modal dialog traps keyboard focus inside it and restores focus to the trigger button when closed, following the ARIA Authoring Practices Guide pattern.
More examples
Semantic button and label
Uses a real button element with aria-pressed and a descriptive aria-label so assistive tech announces the state.
function ToggleFavorite({ isFavorite, onToggle }) {
return (
<button
onClick={onToggle}
aria-pressed={isFavorite}
aria-label={isFavorite ? 'Remove from favourites' : 'Add to favourites'}
>
{isFavorite ? 'β
' : 'β'}
</button>
);
}Form field linked to error
Connects the error message to the input via aria-describedby and marks the field invalid with aria-invalid.
function EmailField({ id, value, onChange, error }) {
const errorId = `${id}-error`;
return (
<div>
<label htmlFor={id}>Email</label>
<input
id={id}
type="email"
value={value}
onChange={onChange}
aria-invalid={!!error}
aria-describedby={error ? errorId : undefined}
/>
{error && <p id={errorId} className="error">{error}</p>}
</div>
);
}Live region for status updates
Uses aria-live='polite' to announce status changes to screen readers without disrupting the current reading flow.
function SaveStatus({ status }) {
return (
<p
role="status"
aria-live="polite"
aria-atomic="true"
className="save-status"
>
{status === 'saving' && 'Saving...'}
{status === 'saved' && 'Changes saved.'}
{status === 'error' && 'Save failed. Try again.'}
</p>
);
}
Discussion