Select — accessibility
Keyboard
- Trigger (
button): Tab focuses it; ArrowDown, ArrowUp, Enter or Space open the menu;disabledremoves it from the tab order. - Open menu: focus moves to the selected option (else the first enabled one) after a 0ms timeout; ArrowDown/ArrowUp move with wrap and skip disabled options; Home/End jump; Enter or Space pick (native button click); Esc closes and returns focus to the trigger.
- Picking an option closes the menu and refocuses the trigger.
- No type-ahead (typing a letter does not jump to an option) and no Tab trapping — Tab leaves the menu while it stays open until the scrim is clicked or Esc.
- Scrim closes on
mousedown; window scroll (of a scroll parent of the anchor) and resize also close.
ARIA
- Trigger:
role="combobox",aria-haspopup="listbox",aria-expanded,aria-requiredfromrequired;aria-label,aria-labelledby,aria-describedbyare passed through — inside<Field>use the label id. - Menu:
<Popover role="listbox" aria-label>; rows arebutton[role="option"]witharia-selectedand nativedisabled. - The hidden native
selectisaria-hidden="true"andtabindex="-1"; it exists only for the form (constraint validation bubble, FormData). - No
aria-activedescendant— focus really moves between option buttons.
Focus
- Trigger
:focus-visiblegets the foundation ring fromds.css:outline: 2px solid var(--accent); outline-offset: 1px. The.is-openclass adds the accent border + 3px halocolor-mix(in srgb, var(--accent) 16%, transparent). - Option buttons inside the menu get the same foundation outline (they match
button:focus-visible). - Focus returns to the trigger on Esc and on pick; it does not return when the menu closes through the scrim, scroll or resize (focus stays on the last focused option, which is removed from the DOM → body).
Contrast
Computed from packages/ui/src/tokens/tokens.css (WCAG 2.x relative luminance):
- Trigger value
--ink-900#18191b on--surface-100#ffffff = 17.59:1 (light); #eceded on #1b1c1f = 14.53:1 (dark). - Placeholder
--fg-muted#98999c on white = 2.85:1 (light) — below AA 4.5:1; #717479 on #1b1c1f = 3.63:1 (dark). - Disabled trigger text
--fg-mutedon--surface-75#fbfbfb = 2.75:1 (light), 3.82:1 (dark). - Trigger border
--border-strong#dcdbdd on white = 1.38:1 (light), #3a3c42 on #1b1c1f = 1.55:1 (dark) — hairline, below the 3:1 boundary minimum. - Open/focus border
--accent#0072f5 on white = 4.44:1; on #1b1c1f = 3.83:1 — passes 3:1 for a non-text indicator. - Error border
--critical#e5484d on white = 3.91:1 (light), 4.35:1 (dark). - Row text
--fg=--ink-900on the menu surface = 17.59:1 (light), 14.53:1 (dark); on hover--hover-surface= 17.0:1 (light, #fbfbfb) / 13.09:1 (dark, #232529). - Accent check
--accenton white = 4.44:1 (light), 3.83:1 (dark). - Disabled row
opacity: 0.4—--ink-900blended on white ≈ #a3a4a5 ≈ 2.6:1.
Known limits
- Placeholder and disabled text (
--fg-muted) are below AA 4.5:1 in light theme. - No type-ahead; long lists rely on arrow keys — the reason the family caps Select at ~10 items.
- Closing via scrim/scroll/resize drops focus to
body. - Option buttons are not a single tab stop: Tab walks through them (each is a real
button), which differs from the WAI-ARIA listbox pattern. - Native constraint-validation bubble anchors to the sr-only select (fixed, no top/left) — it appears at the element's static position, not always next to the trigger.
multiple: every option reports its ownaria-selected; Enter / Space toggles the focused option without closing the menu, Escape closes it and returns focus to the trigger, which shows the summary („3 vybráno“). The listbox setsaria-multiselectable="true"(acrm !4501), so a screen reader announces that several options can be chosen; a single select does not set it.
Source: docs/select/select.a11y.md · GitLab