Guides

Accessibility

Labeling, keyboard behavior, focus management, and live-region support in the Liquid Glass components.

Accessibility is built into the behavior layer, not the glass styling. The interactive components provide roles, states, keyboard interaction, focus management, and native form integration. The visual layer only changes the surface.

This guide describes the guarantees you can rely on and the decisions you still need to make in your application.

Label every control

ComponentAccessible name source
InputField label, or aria-label / aria-labelledby
Checkboxlabel, or aria-label / aria-labelledby
CheckboxGrouplabel, or aria-label / aria-labelledby; each item has its own label
Switchlabel, or aria-label / aria-labelledby
RadioGrouplabel, or aria-label / aria-labelledby
Selectlabel, or aria-label / aria-labelledby
Slideraria-label / aria-labelledby, or getAriaLabel per thumb
Progresslabel, or aria-label / aria-labelledby
Tabsaria-label / aria-labelledby on the tab list
Togglelabel, or aria-label / aria-labelledby
ToggleGrouparia-label / aria-labelledby
ScrollAreaaria-label / aria-labelledby on the viewport
MenuTrigger label, or aria-label / aria-labelledby for icon-only triggers
TooltipTrigger label; the tooltip content is supplementary
Dialog, AlertDialog, DrawerRequired title prop
PopoverRequired title prop

Fieldset gives a group of related fields a shared accessible name from its legend; the controls inside still need their own labels.

Visible labels are preferred. A label rendered outside the component is fine as long as it is associated correctly:

<span id="volume-label" className="text-sm text-ui-ink">
  Volume
</span>
<Slider aria-labelledby="volume-label" defaultValue={40} />

For a range slider, pass getAriaLabel so each thumb gets a distinct name:

<Slider
  aria-label="Price range"
  defaultValue={[20, 80]}
  getAriaLabel={(index) => (index === 0 ? "Minimum price" : "Maximum price")}
/>
Receive notificationsWhen enabled, new messages are sent to your inbox.

Switch and Checkbox use the visible label as the accessible name and link the description through aria-describedby. CheckboxGroup names the group and each option separately, and Tab moves between its checkboxes. Decorative icons inside Button, Input, and other components are marked aria-hidden="true".

Keyboard behavior

The components follow these native interaction patterns:

InteractionExpected behavior
Tab / Shift+TabMove focus between controls
Enter / SpaceActivate buttons, toggles, checkboxes, switches, tabs, menu items, and select options
Arrow keysMove slider thumbs, navigate toggle groups and tabs, and move through menu items or select options
EscapeClose a dialog, alert dialog, drawer, popover, menu, select, or tooltip
Home / EndMove a single-value slider to its bounds (range thumbs move to the adjacent thumb boundary), or jump to the first or last tab
Page Up / Page DownMove a slider by largeStep
TypeaheadMatch labels in a menu or select

Accordion triggers use real buttons and expose aria-expanded; they follow the document tab order. loopFocus and orientation are deprecated and no longer change keyboard focus. Toggle groups support single and multiple selection with the expected arrow-key roving focus. Tabs use roving focus inside the tab list: arrow keys follow the orientation, Home and End jump to the ends, and Enter or Space activates the focused tab. Set activateOnFocus to switch panels as focus moves. Scroll areas keep native wheel, trackpad, and keyboard scrolling; the custom scrollbars are decorative and hidden from assistive technology.

Menu and Select use roving focus inside their popups: arrow keys move through enabled items, typeahead matches labels, and Escape returns focus to the trigger. Disabled items stay announced but cannot be activated. Tooltip opens on hover or keyboard focus and closes on blur or Escape; it is visual-only and not announced as a description, so icon-only triggers still need their own accessible name.

Overlays and focus

Dialog, AlertDialog, and Drawer are modal:

  • Focus moves into the popup when it opens.
  • Tab stays inside the popup while it is open.
  • Escape closes all three overlays. Backdrop dismissal also closes Dialog and Drawer unless disabled; AlertDialog always blocks it.
  • Focus returns to the trigger after closing.
  • Page scroll is locked while the overlay is open.

Popover is deliberately non-modal: focus is not trapped, the page stays scrollable, and outside press or Escape closes it. Use it for compact settings, filters, and contextual actions, not for multi-step flows that need the user's full attention.

Menu and Select are modal by default: outside interaction is locked while their popup is open, and Escape closes the popup and returns focus to the trigger. Set modal={false} only when the surrounding page must stay interactive.

AlertDialog is for destructive or irreversible decisions. Use it instead of a plain Dialog when the user must choose between explicit actions.

Toasts and live regions

ToastProvider renders a toast viewport with live-region semantics. Keep one provider near the application root, and render <Toast /> after the app content so the viewport is available.

  • Use type to add a status icon: info, success, warning, error, or loading.
  • Use priority: "high" for an assertive announcement, such as a failed save.
  • Keep messages short and actionable. Do not move focus to a toast.
  • Provide an action through actionProps when the user can undo or retry.
  • Leave timeout long enough to read the message, or set timeout: 0 when the toast requires action.

toast.promise starts with a loading state and updates the same toast when the promise settles, which avoids announcing duplicate messages.

Motion and contrast

All decorative transitions include motion-reduce variants. When the user sets prefers-reduced-motion: reduce, the layout and state changes remain but the motion is removed or shortened.

The glass surface is translucent. Check text contrast against the actual background, especially over images, gradients, and busy dashboards. The optional glass preset plugin provides data-glass="thick" for a stronger shared veil without changing component code. The default monochrome palette is designed for a quiet interface; custom tokens should keep the same contrast relationship in both themes.

Testing checklist

  • Tab through the page without a mouse and confirm every control is reachable.
  • Confirm focus returns to the trigger after closing a dialog or drawer.
  • Read the form with a screen reader: names, descriptions, and errors should be announced once, in context.
  • Check that a Switch and Checkbox announce their checked state.
  • Confirm a CheckboxGroup announces its name and each option once.
  • Verify a range slider announces both thumb values and their names.
  • Open a Menu and a Select with the keyboard and confirm arrow keys, typeahead, and Escape behave as expected.
  • Focus an icon-only Tooltip trigger and confirm its accessible name.
  • Trigger a toast and confirm the message is announced without stealing focus.
  • Run the page with reduced motion enabled and with a high-contrast theme.
  • Test with your real content; placeholder text often hides labeling problems.

Accessibility is a shared responsibility. The components provide the interaction foundation, but the surrounding headings, error messages, page structure, and content still belong to your application.