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; withonSubmit(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-labelfromariaLabel— WAI-ARIA 1.2 combobox pattern. - Menu:
role="listbox"witharia-label(common:mention.aria); rowsrole="option"+aria-selectedon the active one; the empty state isrole="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-500underline) has no keyboard equivalent inside the editor.
Contrast
Computed from packages/ui/src/tokens/tokens.css:
- Text
--fgon--surface-100= 17.59:1 (light); dark editor keeps--surface-100#1b1c1f (no--surface-50override 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-secondaryvalues. - Placeholder
--fg-muted= 2.85:1 (light), 3.63:1 (dark). - Menu row name
--ink-900on--surface-100= 17.59:1; secondary.em--fg-secondary300 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 inmention-input.css. - Dark theme keeps
--surface-100for the editor while.acrm-inputswitches to--surface-50; the two fields differ by one step in dark. - The menu depends on JS caret measurement; browser zoom or
visualViewportchanges 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