AI assistant — accessibility

Keyboard

  • Every control is a real <button>: the four head actions, the trace toggle, the four answer actions, the slash button and send. Tab reaches them in reading order.
  • The composer is a multiline input: <kbd>Enter</kbd> sends, <kbd>Shift</kbd>+<kbd>Enter</kbd> makes a new line. Typing <kbd>/</kbd> at the start opens the prompt menu; the slash button does the same for the mouse. These are the only two shortcuts the panel owns.
  • The trace toggle is operable from the keyboard and carries aria-expanded, so the collapsed state is both announced and controllable.
  • The panel does not trap focus — it is a column of the page, not a modal — and it does not move focus when it opens.

ARIA

  • section.aichat-panel is labelled AI asistent, so the panel is findable as a region.
  • The thread body is role="log" with aria-live="off". That is deliberate: an answer streams token by token, and a live log would read every fragment. The cost is that a completed answer is not announced at all — AssistantChat mounts a separate RunAnnouncer for that, and it is the only thing that speaks.
  • The feedback buttons carry aria-label and aria-pressed, so the active thumb is announced as pressed and re-pressing it clears the vote.
  • The head buttons are named by title only; the kebab additionally carries aria-haspopup="menu" and aria-expanded. The Anonymous mode row is a menuitemcheckbox and keeps the menu open; the model choices are menuitemradio; Thread history and Model open a drill-in level headed by a back row.
  • The tool trace is a list of spans with no roles — it is a summary of what happened, not a structure to navigate.
  • The sparkle mark is decorative.

Focus

  • All controls take the foundation ring from ds.css (outline: 2px solid var(--accent); outline-offset: 1px); neither assistant.css nor ai-chat.css switches it off.
  • Focus ring --accent: 4.44:1 light, 3.83:1 dark.
  • The scroll-to-bottom button appears and disappears with scroll position, so a tab stop comes and goes while the user reads.

Contrast

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

  • The answer text is ink on the canvas — 17.59:1 light, 14.53:1 dark.
  • The question block is ink on --surface-75, which is a 1.03:1 ground against the canvas: the block is distinguished by its shape and alignment far more than by its colour. That is intentional (it is meant to be quiet), but it means the boundary between question and answer is carried almost entirely by position.
  • Tool labels and the typing line use the muted tone — 2.85:1 light, 3.63:1 dark, below AA.

Known limits

  • thumb-up, thumb-down and player-stop are missing from the icon set and fall back to info-circle. The two feedback buttons therefore draw the same glyph. Their aria-labels still differ, so a screen-reader user can tell them apart — but a sighted user cannot, which inverts the usual failure. Verified in the browser.
  • aria-live="off" means the thread itself never announces; everything depends on RunAnnouncer.
  • The question/answer boundary is positional and low-contrast.
  • Tool labels sit below AA.
  • No shortcut focuses the panel or the composer from the page.

Source: docs/ai-assistant/ai-assistant.a11y.md · GitLab