Skip to content

fix(site/src): make text-only tooltip triggers keyboard focusable - #30403

Merged
chrifro merged 3 commits into
mainfrom
a11y-focusable-tooltip-triggers
Oct 9, 2026
Merged

chrifro merged 3 commits into
mainfrom
a11y-focusable-tooltip-triggers

Conversation

@chrifro

@chrifro chrifro commented Oct 6, 2026

Copy link
Copy Markdown
Contributor

🤖 This PR was created by Coder Agents on behalf of @chrifro.

Several tooltips used non-focusable triggers (div, span, bare svg), so keyboard and screen reader users could never open them or hear their content. Radix only links tooltip content to its trigger via aria-describedby while open, and it opens on hover or focus. These triggers are now native <button type="button"> elements with a focus ring, following the existing NetworkCallBadges and #30342 pattern.

Addresses the text-only tooltips from the Deque audit (custom-tooltip rule, WCAG 4.1.2):

Ticket Element
DEQ-579 Deployment banner: status icon, Transmission, data sent to/from workspaces, latency, last aggregated
DEQ-283 Notification delivery method icons (SMTP, webhook)
DEQ-226 AI Gateway sessions: Threads info icon
DEQ-287 (partial) AI Gateway session: Session summary info icon, in/out token badges

DEQ-393 (+N more roles) was already fixed by #30342. The tooltips in DEQ-287, DEQ-473 and DEQ-378 that contain links need a popover/toggletip pattern and will follow in a separate PR.

Before / after (keyboard Tab)

Before After
Footer: Tab after the last workspace status link before Focus skips to VS Code after Focus lands on Transmission, tooltip opens
Notifications: Tab after a template switch before Focus skips to the next switch after Focus lands on SMTP, tooltip opens
Implementation notes and verification
  • The token badges button has no aria-label, so its visible counts stay in the accessible name (WCAG 2.5.3 Label in Name). The full breakdown is exposed through the tooltip description.
  • Icon-only triggers get names: "Deployment status", "About threads", "About sessions", and the delivery method label.
  • In the AI Gateway sessions list, the token badge sits inside a clickable row, so Enter on the badge now opens the row, matching what a mouse click already did.
  • Layout: element positions in the footer and notifications page match main exactly. Converted triggers are 16px tall (was 12 to 14px) with the same vertical center, matching the existing Active Connections buttons.
  • Tabbing through the footer in a real browser, every trigger from the audit now takes focus and its aria-describedby resolves to the tooltip text.
  • AI Gateway pages are paywalled in the dev deployment, so those triggers were verified by rendering their Storybook stories in Vitest and checking focus plus accessible description.
  • Added a Vitest regression test in DeploymentBannerView.test.tsx. It fails on main and passes here.

Several tooltips used non-focusable triggers (div, span, bare svg), so
keyboard and screen reader users could never open them or hear their
content. Render those triggers as native buttons so Radix exposes the
tooltip via aria-describedby on focus.

Covers the deployment banner stats, notification delivery method icons,
and the AI Gateway threads info, session summary info, and token usage
badges.
@linear-code

linear-code Bot commented Oct 6, 2026 •

Copy link
Copy Markdown

DEQ-579

DEQ-283

DEQ-226

DEQ-287

@aqandrew aqandrew left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very nice! Just 2 small non-blocking nits about how classnames are written + a focus ring that looks off

<button
type="button"
aria-label="Deployment status"
className={`${tooltipTriggerClassName} ml-3 flex items-center justify-center`}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Prefer combining classnames with cn instead of interpolating strings. This goes for all 6 usages of tooltipTriggerClassName in this file

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Switched all 6 usages to cn(tooltipTriggerClassName, ...) in 93a27e9.

Generated by Coder Agents.

Comment on lines +31 to +37
<button
type="button"
aria-label="About sessions"
className="m-0 shrink-0 flex items-center border-0 bg-transparent p-0 text-inherit rounded-sm focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-content-link"
>
{children}
</button>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: IMO the focus ring should not include the margin to the left of the info icon

Image

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved the ml-2 from the icon onto the trigger button, so the focus ring now wraps only the icon. Fixed in 93a27e9.

Generated by Coder Agents.

@chrifro
chrifro merged commit 5b51a27 into main Oct 9, 2026
27 checks passed
@chrifro
chrifro deleted the a11y-focusable-tooltip-triggers branch October 9, 2026 08:53
@github-actions github-actions Bot locked and limited conversation to collaborators Oct 9, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants