Check menu — accessibility

Keyboard

  • Trigger supplied by the caller (button): Enter/Space open and close the menu through the anchor span's click.
  • On open, focus moves to the search field when there is one, otherwise to the first row.
  • ↑/↓, Home, End move focus between rows (canonical Popover behaviour). They do nothing while focus is in the search field — Tab moves from the search to the rows.
  • Enter or Space toggles the focused row; the menu stays open and focus stays on the row, so repeated toggling works. Keyboard activation (click.detail === 0) is never swallowed by the after-drag guard.
  • With onReorder: Alt+↑ / Alt+↓ moves the focused row one place. Focus stays on the moved row, so the key can be repeated. At the edge nothing moves, but the position is still announced. Popover ignores arrows with a modifier, so Alt+arrow never moves focus. Reorder is off while a query is typed.
  • Esc closes the menu and returns focus to the trigger; the key is consumed, so an enclosing dialog stays open.
  • Phone sheet: Hotovo (full width, 44px) and a tap on the scrim close it; no keyboard needed.

ARIA

  • Shell: role="menu" + aria-label (ariaLabel); the phone sheet uses the same role and label, and shows the label as its title.
  • Rows: button[role="menuitemcheckbox"] + aria-checked; the checkbox and icon are decorative.
  • Search: input with aria-label = the placeholder.
  • The group line is role="separator".
  • Reorder announcement: a visually hidden role="status" aria-live="polite" region next to the trigger (outside the menu, which may contain only items) says "{label}: pozice {pos} z {total}" after every Alt+arrow.
  • Draggable rows carry draggable and data-check-item; the insertion line is aria-hidden.
  • The trigger must set aria-haspopup="menu" and aria-expanded={open} itself.

Focus

  • Trigger and rows: foundation ring outline: 2px solid var(--accent); outline-offset: 1px.
  • Focus returns to the trigger on Esc; a click outside leaves focus where the user clicked.

Contrast

Computed from packages/ui/src/tokens/tokens.css:

  • Row label --ink-900 on --surface-100 = 17.59:1 (light), 14.53:1 (dark).
  • Icon --fg-secondary = 3.93:1 / 6.2:1 (non-text).
  • Checkbox on --accent = 4.44:1 (light) / 3.83:1 (dark); off border --border-strong 1.38:1 / 1.55:1.
  • Empty state --fg-muted on --surface-100 — secondary text.
  • Dragged row opacity: 0.45 ≈ 2.9:1 (light) — transient.

Known limits

  • Drag and drop is mouse only; on touch the order is changed elsewhere (e.g. the column ⋮ menu). Keyboard reorder (Alt+↑/↓) covers the desktop without a mouse.
  • The off-state checkbox border is a hairline (below 3:1); the state is carried by aria-checked.
  • Trigger ARIA depends on the caller.
  • The component is app-side; the catalog shows markup, not the live component.

Source: docs/select/check-menu.a11y.md · GitLab