Utilities
Helpers for accessibility, layout, and shared patterns that don't fit a specific component category.
Components
| Component | Purpose |
|---|---|
| Screenreader-only | Visually hidden content accessible to screen readers |
| Skip navigation | Bypass repeated content for keyboard users |
| Focus trap | Contain focus within a modal or dialog |
| Visually hidden input | Custom checkbox/radio styling with accessible native input |
| Live region | Dynamic content announcements for screen readers |
Screenreader-only
Hides content visually while keeping it in the accessibility tree. Implemented as the <VisuallyHidden> component.
- Used for descriptive labels on icon-only buttons
- Used for additional context that sighted users get visually
- Never use
display: noneorvisibility: hiddenfor content that should be read asChildprop mergessr-onlyclass onto the child element instead of wrapping in a<span>
Props
| Prop | Type | Default | Description |
|---|---|---|---|
children | ReactNode | (required) | Content to hide visually |
asChild | boolean | false | Merge onto child element instead of wrapping |
className | string | — | Additional classes |
Usage
- Icon-only buttons: add a
<VisuallyHidden>label inside the button alongside the icon - Data tables: add screenreader-only context to column headers where the visual context is ambiguous
- Navigation: provide descriptive text that supplements visual cues
Screenreader-only
Icon button with VisuallyHidden label
<VisuallyHidden>Visible: Account balance (Current balance for your primary checking account)
Skip navigation
A link that becomes visible on keyboard focus, allowing users to bypass repeated navigation.
- First focusable element on the page
- Links to the main content landmark
- Visible only on
:focus— hidden by default - Must pass WCAG 2.4.1 (bypass blocks)
Skip navigation link
Tab into the box below to reveal the skip link:
Focus trap
Contains keyboard focus within a specific area (dialog, modal, drawer).
- Tab cycles through focusable elements inside the trap
- Shift+Tab moves backwards within the trap
- Focus returns to the trigger element when the trap is released
- Built into the Dialog and Popover components via
@base-ui/react
Visually hidden input
Native <input> elements positioned off-screen for custom styled checkboxes and radios.
- Maintains native keyboard behavior and form submission
- Screen readers announce the native input, not the visual replacement
:checkedstate drives visual styling via CSS sibling selectors- Used when the default Checkbox/Switch components don't fit the design
Live region
Announces dynamic content changes to screen readers via aria-live.
politeness:"polite"(default) waits for the user to finish,"assertive"interrupts immediatelyatomic:true(default) reads the entire region,falsereads only the changed nodesrelevant: controls which mutations trigger announcements —"additions","removals","text", or"all"role: semantic roles"status","alert","log","timer"—"alert"and"status"implyaria-liveso it is omittedvisuallyHidden:true(default) appliessr-only— set tofalsewhen the live content is also visible
Props
| Prop | Type | Default | Description |
|---|---|---|---|
politeness | "polite" | "assertive" | "polite" | Urgency level of the announcement |
atomic | boolean | true | Read entire region or only changes |
relevant | "additions" | "removals" | "text" | "all" | — | Which mutations to announce |
role | "status" | "alert" | "log" | "timer" | — | Semantic role (some imply aria-live) |
visuallyHidden | boolean | true | Apply sr-only styling |
Usage
- Transfer confirmations: polite announcement after form submit
- Error alerts: assertive announcement with
role="alert" - Chat/log feeds:
role="log"withrelevant="additions" - Timers:
role="timer"for countdown displays
Live region
Click the button to trigger a screen reader announcement:
politeness="polite"politeness="assertive"role="status"role="alert"role="log"role="timer"Design and accessibility
- Screenreader-only content must be meaningful and concise
- Test with actual screen readers (VoiceOver, NVDA) to verify announcements
- Skip navigation must be the first focusable element
- Focus traps must release focus on Escape and return to trigger
- Never remove focus outlines without providing an alternative indicator
- Prefer
politelive regions — useassertiveonly for errors or critical alerts - Live regions must exist in the DOM before content changes; do not conditionally render the container
- Keep announcements brief — screen readers cannot be interrupted mid-sentence by a new polite announcement