Mention input — accessibility

Keyboard

  • Tab focuses the editor (contenteditable); typing edits. Single-line: Enter is swallowed (preventDefault), so no line break.
  • Multi-line: Enter inserts a new line; with submitOnModEnter ⌘/Ctrl+Enter submits; with onSubmit (chat) Enter submits and Shift+Enter breaks the line. IME composition Enter never submits.
  • Menu open: Up/Down move the active row (scrolled into view), Enter or Tab pick it (or just close the menu when there are no results), Esc closes — Esc is handled even during IME composition.
  • Chips are not tab stops; a chip is deleted like a character with Backspace/Delete and opened by pointer click. Arrow keys move over the chip as one unit (contenteditable="false").

ARIA

  • Editor: role="combobox", aria-expanded (menu open), aria-haspopup="listbox", aria-controls = menu id while open, aria-activedescendant = the active option id, aria-multiline = multiline, aria-label from ariaLabel — WAI-ARIA 1.2 combobox pattern.
  • Menu: role="listbox" with aria-label (common:mention.aria); rows role="option" + aria-selected on the active one; the empty state is role="presentation" so it is not an option.
  • .mi-sr (role="status", aria-live="polite", visually hidden, position: fixed) announces the result count and empty states outside the listbox.
  • Chips inside the editor are <span data-…> — screen readers read the name as text; the record type is not announced in the chip.
  • MentionText (read-only) renders the same markup without the combobox role.

Focus

  • .mi-editor:focus = border-color: var(--accent) + box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 15%, transparent). Note the 15% mix — Input uses 16%; a 1% drift to align in the CSS.
  • Focus stays in the editor while the menu is open (active descendant pattern); the menu never takes focus. Pointer-down outside closes it.
  • Chips have no focus state of their own; the hover state (--ink-900 + --ink-500 underline) has no keyboard equivalent inside the editor.

Contrast

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

  • Text --fg on --surface-100 = 17.59:1 (light); dark editor keeps --surface-100 #1b1c1f (no --surface-50 override here) → #eceded on #1b1c1f ≈ 14.9:1.
  • Chip name --fg-secondary #7d8184 on white = 3.93:1 (light, 12px) — below AA 4.5:1; #9a9ca1 on #1b1c1f = 6.20:1 (dark). The dashed underline --border-strong = 1.38:1 / 1.55:1.
  • Lost entity .mi-lost = same --fg-secondary values.
  • Placeholder --fg-muted = 2.85:1 (light), 3.63:1 (dark).
  • Menu row name --ink-900 on --surface-100 = 17.59:1; secondary .em --fg-secondary 300 12px = 3.93:1 (light) — below AA at that weight.
  • Focus border --accent = 4.44:1 (light), 3.83:1 (dark).

Known limits

  • Chip name at 12px --fg-secondary (3.93:1 light) and the 300-weight secondary row text are below AA.
  • Chips cannot be reached or opened from the keyboard inside the editor; opening the record is pointer-only.
  • The editor carries box-shadow: var(--shadow-xs) at rest — a resting elevation the Input rule no longer has (fields are flat since MR !198); drift to fix in mention-input.css.
  • Dark theme keeps --surface-100 for the editor while .acrm-input switches to --surface-50; the two fields differ by one step in dark.
  • The menu depends on JS caret measurement; browser zoom or visualViewport changes re-measure via rAF, but a hidden caret (e.g. inside a scrolled-away line) closes it.

Source: docs/text-field/mention-input.a11y.md · GitLab