Skip to content

fix: make the header user menu reachable by keyboard - #478

Open
chennaji9 wants to merge 1 commit into
oss-compass:mainfrom
chennaji9:fix/header-user-menu-keyboard
Open

chennaji9 wants to merge 1 commit into
oss-compass:mainfrom
chennaji9:fix/header-user-menu-keyboard

Conversation

@chennaji9

Copy link
Copy Markdown

Problem

The signed-in header menu opens only on pointer hover: the avatar trigger is a plain <div>, so keyboard users can never focus it, and the menu entries (subscribe, my models, personal settings, system admin) stay display:none — outside the accessibility tree — until a hover happens. The sign-out action is a <div> with a click handler and cannot be activated without a mouse.

Root cause

Menu visibility relies solely on the group-hover:block CSS class; neither the trigger nor the sign-out action are focusable/activatable elements.

Changes

  • apps/web/src/common/components/Header/User.tsx:
    • render the avatar as a <button type="button"> with aria-haspopup="menu" and an accessible label (user name, falling back to the profile-setting string);
    • open the menu on group-focus-within in addition to group-hover, so focusing the trigger (and tabbing through the entries) keeps the menu visible;
    • render the sign-out action as a <button type="button">.

Visual appearance is unchanged apart from the focus opening the menu.

Contract impact

  • paired backend change: no (sign-out mutation unchanged)

Testing

  • New User.test.tsx (auth store, graphql mutation, router and next/image mocked): the trigger is a button named after the user with aria-haspopup="menu"; sign-out is a button; the menu entries are links. The trigger/sign-out role assertions fail against the div-based markup (verified red) and pass with the fix.
  • Full workspace suite jest --ci: 116 passed (113 pre-existing + 3 new).
  • tsc --noEmit (apps/web) clean; eslint clean; prettier --check clean for the changed lines.
  • Not run: manual keyboard pass in a real browser (focus-within opening is CSS-driven and asserted at DOM level). Local Node 22; CI Node 18 parity not run.

🤖 Generated with Claude Code

The signed-in header menu opens only through the group-hover CSS class:
the avatar trigger is a plain div, so keyboard users can never focus it,
and while the entries are links they stay display:none and outside the
accessibility tree until a pointer hover. The sign-out action is a div
with a click handler and cannot be activated without a mouse either.

Render the avatar as a button with aria-haspopup and open the menu on
focus-within in addition to hover, and render the sign-out action as a
button so it is focusable and activatable from the keyboard.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: 陈那几 <2078634206@qq.com>
@vercel

vercel Bot commented Oct 4, 2026

Copy link
Copy Markdown

@chennaji9 is attempting to deploy a commit to the codersett's projects Team on Vercel.

A member of the Team first needs to authorize it.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant