Skip to content

Accessibility

Hydrateless starts from the platform: native elements carry their own roles, keyboard handling, and focus management, and the enhancers add only what the platform can't express yet. Every component is verified in Chromium, Firefox, and WebKit with axe, with JavaScript both off and on.

This page maps each component to its WAI-ARIA Authoring Practices (APG) pattern, lists the keyboard support, and explains the policies that apply across the library.

Pattern mapping

ComponentAPG patternNative basisEnhancer adds
AccordionAccordion<details>/<summary>Single-open behavior, header arrow navigation
DisclosureDisclosure<details>Observable open state only
TabsTabsRadios (CSS baseline) or role="tab" buttonsRoving tabindex, arrow/Home/End, manual or automatic activation, aria-selected
Dropdown MenuMenu Button<button popovertarget> + [popover]role="menu", focus into items, typeahead, checkable items, focus return
MenuMenubar<ul role="menubar">Roving tabindex, submenu control, aria-haspopup="menu", aria-expanded
Modal, DrawerDialog (Modal)<dialog> + showModal() via Invoker Commandsaria-labelledby, scroll lock
PopoverDisclosure-style non-modal surface[popover] + popovertargetaria-expanded/aria-controls on invokers
TooltipTooltiprole="tooltip" + aria-describedbyDelays, top-layer popover="manual", Escape from anywhere
ComboboxCombobox (list autocomplete)<input> + role="listbox"aria-activedescendant, filtering, disabled skipping
Command PaletteCombobox + Listbox<input> + role="listbox"Filtering, group hiding, PageUp/PageDown, Escape policy
SwitchSwitch<input type="checkbox" role="switch">None
Radio GroupRadio GroupNative radios sharing a nameNone
Segmented ControlRadio GroupNative radios under role="radiogroup"None
SliderSlider<input type="range">None
BreadcrumbBreadcrumb<nav aria-label> + <ol> + aria-currentNone
ToastAlert and live regionsrole="status" regionTimers that pause on hover/focus, role="alert" for danger
AlertAlertrole="alert" or role="status"None
Table of ContentsNavigation landmark<nav> + list of linksGenerated links, aria-current scroll spy
Skip LinkBypass Blocks (WCAG 2.4.1)<a href="#main">None

Keyboard support

Keys marked "native" come from the browser; the rest are added by the enhancer.

Tabs

KeyAction
TabMoves into the tab list (selected tab) or on to the panel
Left / Right (Up / Down vertical)Move focus to the previous/next enabled tab, wrapping
Home / EndFirst / last enabled tab
Enter / SpaceActivate the focused tab (activation: 'manual', the default)

With activation: 'automatic' the arrow keys activate as they move.

Accordion

KeyAction
Enter / SpaceToggle the focused header (native <summary>)
Down / UpMove focus to the next / previous header, wrapping
Home / EndFirst / last header
KeyAction
Enter / SpaceOn the trigger: open (native popovertarget). On an item: activate
Down / UpOn the trigger: open and focus the first / last item. In the menu: move, skipping disabled items
Home / EndFirst / last enabled item
Printable charactersTypeahead to the next item starting with those characters
EscClose and return focus to the trigger
TabClose, return focus to the trigger, then let the browser move on
KeyAction
Right / Left (Down / Up vertical)Move between top-level items
Down, Enter, Space (Right vertical)Open the focused item's submenu and focus its first item
Up (horizontal)Open the submenu and focus its last item
Down / Up in a submenuMove between submenu items, skipping disabled ones
Right / Left in a submenuClose and open the adjacent top-level submenu (Left closes in vertical menus)
Home / EndFirst / last item at the current level
EscClose the submenu and return focus to its trigger

Combobox

KeyAction
TypingFilters the list and highlights the first match
Down / UpOpen the list if closed, then move the highlight, skipping disabled options
Alt + DownOpen the list without moving the highlight
PageDown / PageUpMove the highlight ten options
Home / EndMove the text caret (not intercepted, per the APG)
EnterCommit the highlighted option
EscClose the list
TabClose the list and move on

Command Palette

KeyAction
TypingFilters commands (text plus data-hl-keywords), hides empty groups
Down / UpMove the active command, wrapping
PageDown / PageUpMove ten commands
EnterRun the active command
EscClear a non-empty query; when empty, close the hosting <dialog>
Cmd/Ctrl + hotkeyOpen the hosting <dialog> and focus the input
KeyAction
TabCycles within the dialog (native focus containment from showModal())
EscCloses (native, with closedby="any")

Popover and Tooltip

KeyAction
Enter / SpaceToggle a popover from its invoker (native popovertarget)
EscClose the popover (native) or dismiss the tooltip (from anywhere while it is shown)
Focus on a tooltip triggerShows the tooltip immediately

Native controls

Switch, Checkbox, Radio Group, Segmented Control, Slider, Select, and Input rely entirely on the browser's keyboard handling: Space toggles, arrows move radios and sliders, Home/End jump sliders to their bounds.

Focus return

Whenever a surface closes because of a keyboard action, focus goes back to the element that opened it:

  • Modal and Drawer: the browser returns focus to the invoker when a showModal() dialog closes.
  • Popover: the browser returns focus to the popovertarget button when a popover closes via Esc.
  • Dropdown Menu: the enhancer returns focus to the trigger on Esc, Tab, and item activation. Light dismiss (clicking elsewhere) leaves focus where the user put it, because moving it would fight their intent.
  • Menu: Esc in a submenu returns focus to the submenu's trigger; activating a leaf item closes the submenu and focuses the trigger.
  • Tooltip: Esc dismisses without moving focus (WCAG 1.4.13).

Live regions

  • The toast region is role="status" with aria-live="polite" and aria-relevant="additions". Toasts are announced when they arrive without interrupting the current announcement. A danger toast also gets role="alert", which is assertive.
  • Alert components use role="alert" for danger/warning content that appears dynamically, and role="status" otherwise. Content present at page load doesn't need a live role.
  • The table of contents marks the heading in view with aria-current="true" rather than announcing every scroll change.

Disabled items

Menu items, combobox options, and tabs can be disabled with the native disabled attribute (on buttons) or aria-disabled="true" (on anything). The enhancers skip them during arrow navigation and typeahead, and refuse to activate them, so keyboard and pointer users get the same result. Disabled controls render at --hl-disabled-opacity and keep a visible border under forced colors.

Forced colors

Under @media (forced-colors: active) (Windows High Contrast), the semantic tokens resolve to system colors and each component adds the rules that forced-color-adjust would otherwise flatten:

  • Buttons, inputs, and selects keep a visible CanvasText border.
  • The selected tab, segment, active option, and checked switch use Highlight and HighlightText.
  • Focus rings use Highlight.

Nothing depends on color alone: selected states also change aria-selected, aria-checked, or aria-current, which assistive technology announces.

Reduced motion

Under prefers-reduced-motion: reduce every duration token is zeroed, so transitions and @starting-style entries complete instantly, and the spinner, loading button, and indeterminate progress bar render as static shapes. See Motion.

Checklist for your own markup

  • Give every role="tablist", role="menubar", role="listbox", and <nav> an aria-label (or aria-labelledby) when a page has more than one.
  • Render initial state in HTML: aria-selected, hidden, open, aria-checked. The enhancers read it, and so do users with JavaScript off.
  • Use <button type="button"> for menu items and tabs so they are focusable and activate on Enter and Space natively.
  • Put popover on dropdown menus and popovertarget on their triggers so they open before hydration.
  • Mark placeholders (Skeleton) aria-hidden="true" and give spinners role="status" with an aria-label.

Released under the MIT License.