Skip to content

fix(TabList): keep TabMenu arrow keys out of the tab strip - #7323

Open
korkt-kim wants to merge 1 commit into
facebook:mainfrom
korkt-kim:fix/tabmenu-arrow-key-scope
Open

korkt-kim wants to merge 1 commit into
facebook:mainfrom
korkt-kim:fix/tabmenu-arrow-key-scope

Conversation

@korkt-kim

@korkt-kim korkt-kim commented Oct 11, 2026 •

Copy link
Copy Markdown
Contributor

User impact

A keyboard user opens a TabMenu, moves to an option, and presses Left or Right. Focus leaves the still-open menu and lands on the outer tab strip. The next key acts on a tab or the menu trigger, not the option the user was choosing.

Problem and solution fit

  • Why it occurs: a menu-item keydown bubbles to TabList. The menu's vertical focus handler leaves horizontal arrows alone, and the outer useListFocus handles them as tab-strip navigation because it has no boundary configured.
  • Why it harms the task: a key pressed inside the overflow menu moves focus away from its options without closing the menu.
  • Why it matters: the visible menu and the keyboard's focus disagree, so the user has to get back into the menu to finish choosing a destination.

The fix gives the tab strip's existing focus hook a boundary, so a nested popup keeps its keys.

Expected behavior and authority

The WAI-ARIA APG menu pattern distinguishes a menu opened by a menu button from a menubar: Right Arrow does nothing on a leaf item when there is no menubar to move through. TabMenu is a menu button with leaf menuitemradio options, not a menubar submenu. Its horizontal arrows must not become navigation in the surrounding tab strip.

useListFocus already documents and implements boundarySelector for ignoring events owned by a nested list. The current component:TabList record governs edge compensation, not this keyboard behavior. No record changes.

Smallest restoration

One option in TabList.tsx: boundarySelector: '.astryx-tab-list, [popover]'. The selector names the common outer root, where the hook's ref lives; role="tablist" is on the inner strip. A key from the menu reaches its popover boundary first, while a key from the strip reaches its own root.

The regression test presses both horizontal arrows on an open menu item and checks that focus stays there and the menu stays open. A companion jsdom test checks that a role="tablist" inside a popover still navigates between its own tabs. A Core patch changeset accompanies them. This adds no API, default, visual change, or new key handling.

Evidence

  • Reproduction before the change: real Chromium 149, core-tablist--with-menu. Open More and focus Reports. Right Arrow moves focus to Home; Left Arrow, tested from Reports again, moves focus to More. The popover remains open in both cases.
  • Result after the change: both keys leave focus on Reports and keep the menu open.
  • Representative unchanged path: in Chromium, menu Up/Down and Home/End still move between options, Escape closes the menu and returns focus to More, and the strip's Left/Right still move between More and Projects.
  • Regression test or other mutation-sensitive proof: the new jsdom case fails against the pre-change TabList.tsx at the focus assertion after Right Arrow. With the change, all 131 targeted tests pass, including that case, tab-strip navigation inside a popover, menu selection and dismissal, and the Tabs DOM accessibility contracts.

Scope

  • One defect is restored.
  • Separable contradictory or unsettled tagalongs were removed or split.
  • No new public API, visual representation, default, or interaction model is hidden under the bug-fix label.
  • Public text and artifacts contain no internal Meta context.

Testing

pnpm exec vitest run packages/core/src/TabList/TabList.test.tsx \
  -t "leaves horizontal arrows to the open menu" --maxWorkers=4
  pre-change TabList.tsx: 1 failed (focus leaves the menu)

pnpm exec vitest run packages/core/src/TabList \
  packages/core/src/hooks/useListFocus.test.tsx --maxWorkers=4
  Test Files 3 passed (3) · Tests 131 passed (131)

ASTRYX_STRICT_LINT=1 pnpm exec eslint packages/core/src/TabList  ✓
pnpm -F @astryxdesign/core typecheck                           ✓
pnpm check:repo                                               ✓
pnpm build                                                    ✓

Chromium checks above used the existing Storybook story against the pre-change source and this branch. WebKit and Firefox were not run.

The PR is draft because full local validation is not green. pnpm lint:strict passed check:repo, then reported 992 errors in the ignored local artifact assets/theme-runtime-lab.generated.js; no TabList lint errors were reported. pnpm test --maxWorkers=4 reported two failures in apps/storybook/.storybook/story-tree.test.ts; the full run was then stopped and is not reported as passing. Those files are outside this diff.

@vercel

vercel Bot commented Oct 11, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
astryx Ready Ready Preview Oct 11, 2026 1:14pm UTC

Request Review

@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Meta Open Source bot. label Oct 11, 2026
@astracat-bot

astracat-bot Bot commented Oct 11, 2026 •

Copy link
Copy Markdown

AI review status for this pull request.

Review status Updated
❓ Needs maintainer input (for maintainers only) Oct 11, 2026, 2:49 PM UTC

@github-actions github-actions Bot added community Authored by a community contributor (not on the eng/design team) needs:code-review High-risk change (new package/component/API) — needs human code review before merge labels Oct 11, 2026
@github-actions

Copy link
Copy Markdown
Contributor

PR Analysis Report

Preview availability: Storybook and Sandbox are not both ready on the exact-head Vercel preview.

No new or modified components detected.

Bundle Size Summary

Package Size (ESM) Size (CJS) Gzipped
@astryxdesign/core N/A 4.9KB 1.2KB

Accessibility Audit

Status: No accessibility violations detected.

Visual Regression

23 of 446 shot(s) changed.

A change here is a question, not a failure: check whether the after is the
picture you intended. Record that review on the PR. Baseline maintenance is an
explicit dispatch of CI; this report never rewrites the baseline or adds a release gate.

component story theme mode pixels
Selector Indicator Space Bottom Sheet Narrow End RTL neutral light 754,077
Selector Indicator Space Bottom Sheet Wide Start neutral light 740,188
Chat Full AI Chat probe light 155,733
Chat Full AI Chat probe dark 155,005
Chat Default neutral dark 33,205
Chat Default neutral light 32,876
Chat Empty State probe light 23,531
Chat Empty State probe dark 22,622
ChatAutoScroll Scroll Behavior Comparison neutral dark 15,378
ChatAutoScroll Scroll Behavior Comparison neutral light 14,107
Chat With Attachments probe light 13,229
Chat With Attachments probe dark 11,065
TableRowExpansion Detail Panel neutral dark 5,129
TableRowExpansion Detail Panel neutral light 4,050
SideNav Resizable in AppShell probe light 3,208
TableGroupedRows Default neutral light 2,133
TableGroupedRows Default neutral dark 2,116
Markdown Default probe dark 897
Markdown Default probe light 897
Markdown Default neutral dark 882

and 3 more.


Generated by PR Enrichment workflow | View full report

@korkt-kim
korkt-kim marked this pull request as ready for review October 11, 2026 14:26

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Approval is waiting on a maintainer spec decision, not on changes from you. The fix looks correct and is well covered by its new tests. No current Astryx spec covers TabList or TabMenu keyboard behavior, so a maintainer needs to provide or identify the spec claim that keys pressed inside a nested popup, such as an open TabMenu, belong to that popup, with Left/Right on a menu option keeping focus there. No code changes are requested right now; thanks for the clear write-up.

[Automated review]

This branch was successfully deployed

1 active deployment
Preview — b3866b93 Deployed Oct 11, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Meta Open Source bot. community Authored by a community contributor (not on the eng/design team) needs:code-review High-risk change (new package/component/API) — needs human code review before merge

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant