Skip to content

fix(focus region): leave Ctrl+Tab and Meta+Tab to the page - #11247

Merged
cixzhang merged 3 commits into
mainfrom
l0/focus-trap-chorded-tab
Nov 11, 2026
Merged

cixzhang merged 3 commits into
mainfrom
l0/focus-trap-chorded-tab

Conversation

@cixzhang

Copy link
Copy Markdown
Contributor

User impact

  • Who: people working in an Astryx modal (Dialog, CommandPalette, BottomSheet, Lightbox, Drawer, MobileNav, Popover) inside a page or host app that uses Ctrl+Tab or Meta+Tab itself, for example to switch its own tabs.
  • Before: on the first or last control of a modal, Ctrl+Tab or Meta+Tab moved focus to the other end of the modal and cancelled the press, so the page never got it. Between the ends it already reached the page.
  • After: Ctrl+Tab and Meta+Tab reach the browser and the page on every control. Tab, Shift+Tab, Alt+Tab and Alt+Shift+Tab still wrap: Safari moves focus on Option+Tab.

Problem and solution fit

  • Why does it happen? The shared focus region treated any Tab as a focus move, including one chorded with Ctrl or Meta, which browsers use for tab and app switching.
  • Why does it harm the task? A host app's own Ctrl+Tab stops working only when focus happens to be on a modal's first or last control.
  • Why does it matter? component:Dialog FR7: the region keeps focus inside the modal. Ctrl+Tab and Meta+Tab do not move focus, so they are not the region's.

The fix is an early return in useFocusRegionContainment's Tab handler for ctrlKey or metaKey, as the records now state.

Expected behavior and authority

  • component:Dialog FR7, now: "Tab and Shift+Tab wrap with or without Alt (Option), since Safari moves focus on Option+Tab; Tab chorded with Ctrl or Meta passes through to the browser."
  • component:Popover's Tab row mirrors it, where Popover's trap applies.

Smallest restoration

  • hooks/useFocusTrap.ts: a 6-line early return.
  • Tests (useFocusTrap.test.tsx, "useFocusTrap chorded Tab"):
    • Ctrl+Tab and Meta+Tab, with and without Shift, at either end are neither wrapped nor default-prevented;
    • Alt+Tab, Alt+Shift+Tab, Tab and Shift+Tab still wrap.
  • Dialog FR7 (claim and verification row) and Popover's Tab row.
  • A [fix] Changeset.

The fix and its first test are vjeux's (fix/focus-trap-chorded-tab), cherry-picked with authorship kept. This PR adds the Alt+Shift+Tab and plain-Tab cases and the records. It builds on main rather than on the open focus-trap drafts #11156 and #11121, which will merge main and resolve a small conflict.

Evidence

  • Red first: on main's useFocusTrap.ts, "leaves Ctrl+Tab and Meta+Tab at either end to the browser" fails. The Alt and plain-Tab cases pass on main and with the fix, as guards.
  • useFocusTrap, Dialog and Popover suites: 344 passed.

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.

Writing rubric results

Run over the Dialog FR7 sentence, its verification row, and the Popover Tab row.

  • R1 narration: none.
  • R2 research in the record: none. The Safari reason is the behavior's constraint.
  • R3 evidence naming a change: none.
  • R4 rejected alternatives over one line or outside a decision: none.
  • R5 pull request or issue references remaining: none.
  • R6 ruled open questions still listing options: none.
  • R7 project status in the record: none.

Co-authored-by: Christopher Chedeau vjeuxx@gmail.com

(Posted by Cindy's agent on her behalf.)

vjeux and others added 3 commits November 11, 2026 06:16
…tate the chorded Tab rule

Dialog FR7 and the Popover Tab row state that Tab and Shift+Tab wrap with
or without Alt (Option), and that Tab chorded with Ctrl or Meta passes
through to the browser. The tests add Alt+Shift+Tab and plain Tab and
Shift+Tab at either end. The changeset credits both authors.
@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Meta Open Source bot. label Nov 11, 2026
@vercel

vercel Bot commented Nov 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 Nov 11, 2026 2:29pm UTC

Request Review

@astracat-bot

astracat-bot Bot commented Nov 11, 2026 •

Copy link
Copy Markdown
Contributor

AI review status for this pull request.

Review status Updated
🟢 Reviewed (for maintainers only) Nov 11, 2026, 3:31 PM UTC

@cixzhang

Copy link
Copy Markdown
Contributor Author

/approve-spec 0bd5bf4

@cixzhang cixzhang added ci:run A maintainer lets this outside contributor's pull request run CI (AST-030 FR17) ci:run-a11y Run the accessibility suite on this PR; write access, next push (AST-030 FR12) labels Nov 11, 2026

@astracat-bot astracat-bot Bot 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.

Approved pending CI: a CI run on this head is still in progress.

The focus region now leaves Ctrl+Tab and Meta+Tab to the browser and the page at the first and last controls of a modal or Popover, while Tab, Shift+Tab and Alt+Tab still wrap. The new test fails against main's hook and passes here, browser checks on the Dialog and Popover stories show the same, and the Dialog FR7 and Popover wording carries an engineering owner's spec approval for this head. No blocking findings.

Automated review (maintainer link)

@github-actions

github-actions Bot commented Nov 11, 2026 •

Copy link
Copy Markdown
Contributor

PR Analysis Report

🔎 Preview · 0bd5bf4

Storybook · Sandbox · built from this PR's head

Modified components

Component Size LoC
Popover 95.1 kB (+19 B) · 14.3 kB CSS (0) —
ChatComposerTokenElement 80.2 kB (+18 B) · 11.3 kB CSS (0) —
BaseTypeahead 76.3 kB (+17 B) · 11.0 kB CSS (0) —
47 more components
Component Size LoC
ChatComposer 82.6 kB (+17 B) · 12.1 kB CSS (0) —
ChatComposerInput 80.2 kB (+17 B) · 11.3 kB CSS (0) —
lab/ChatReactionBar 111.5 kB (+17 B) · 1.1 kB CSS (0) —
ComplexSelector 75.5 kB (+16 B) · 11.9 kB CSS (0) —
DropdownMenu 101.8 kB (+16 B) · 17.6 kB CSS (0) —
MultiSelector 99.3 kB (+16 B) · 16.9 kB CSS (0) —
SideNavHeading 80.2 kB (+16 B) · 11.3 kB CSS (0) —
BreadcrumbItem 89.4 kB (+15 B) · 12.1 kB CSS (0) —
MoreMenu 102.0 kB (+15 B) · 17.6 kB CSS (0) —
SideNav 118.0 kB (+15 B) · 11.4 kB CSS (0) —
TabMenu 76.4 kB (+15 B) · 11.2 kB CSS (0) —
TopNavHeading 79.7 kB (+15 B) · 11.3 kB CSS (0) —
TopNavMenu 79.1 kB (+15 B) · 11.3 kB CSS (0) —
lab/Comments 358.3 kB (+15 B) · 2.5 kB CSS (0) —
AlertDialog 108.9 kB (+14 B) · 14.6 kB CSS (0) —
Lightbox 125.8 kB (+14 B) · 10.6 kB CSS (0) —
MobileNav 73.7 kB (+14 B) · 10.6 kB CSS (0) —
TopNav 75.4 kB (+14 B) · 11.0 kB CSS (0) —
TopNavMegaMenu 74.1 kB (+14 B) · 11.9 kB CSS (0) —
lab/CommentComposer 156.1 kB (+14 B) · 1.9 kB CSS (0) —
lab/TourStep 101.4 kB (+14 B) · 590 B CSS (0) —
lab/TransferListSelector 114.1 kB (+14 B) · 1.4 kB CSS (0) —
richtext/RichTextEditorToolbar 284.0 kB (+14 B) · 454 B CSS (0) —
CommandPalette 75.0 kB (+13 B) · 12.7 kB CSS (0) —
DateInput 192.4 kB (+13 B) · 17.1 kB CSS (0) —
Pagination 115.0 kB (+13 B) · 17.8 kB CSS (0) —
Selector 104.8 kB (+13 B) · 17.5 kB CSS (0) —
SideNavItem 74.9 kB (+13 B) · 11.2 kB CSS (0) —
Typeahead 85.5 kB (+13 B) · 12.5 kB CSS (0) —
Drawer 52.5 kB (+12 B) · 8.3 kB CSS (0) —
DateTimeInput 214.6 kB (+11 B) · 18.8 kB CSS (0) —
lab/ChatEmojiPicker 110.3 kB (+11 B) · 773 B CSS (0) —
DateRangeInput 97.1 kB (+9 B) · 12.5 kB CSS (0) —
Dialog 24.9 kB (+9 B) · 6.9 kB CSS (0) —
AppShell 94.9 kB (+8 B) · 13.5 kB CSS (0) —
Markdown 192.9 kB (+8 B) · 20.8 kB CSS (0) —
Tokenizer 101.2 kB (+8 B) · 19.2 kB CSS (0) —
BottomSheet 69.4 kB (+6 B) · 7.7 kB CSS (0) —
PowerSearch 298.1 kB (+6 B) · 24.5 kB CSS (0) —
Table 129.8 kB (+6 B) · 18.4 kB CSS (0) —
TimeInput 167.2 kB (+6 B) · 16.1 kB CSS (0) —
lab/CommentThread 351.5 kB (+6 B) · 2.4 kB CSS (0) —
TableHeaderCell 113.5 kB (+4 B) · 17.7 kB CSS (0) —
ContextMenu 112.7 kB (+3 B) · 17.5 kB CSS (0) —
TableCell 113.9 kB (+2 B) · 18.0 kB CSS (0) —
BottomSheetSwitcher 18.6 kB (0) · 3.4 kB CSS (0) —
DropdownMenuSubMenu 72.6 kB (0) · 9.7 kB CSS (0) —

Package size: @astryxdesign/core 7.34 MB packed (+248 B); no new dependencies.

Not run on this PR: add ci:run-visual or ci:run-rtl to run them.


Generated by PR Enrichment workflow | View full report

@cixzhang
cixzhang marked this pull request as draft November 11, 2026 15:06
@cixzhang
cixzhang marked this pull request as ready for review November 11, 2026 15:06
@cixzhang
cixzhang merged commit 16d7f39 into main Nov 11, 2026
149 checks passed

This branch was successfully deployed

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

Labels

ci:run A maintainer lets this outside contributor's pull request run CI (AST-030 FR17) ci:run-a11y Run the accessibility suite on this PR; write access, next push (AST-030 FR12) CLA Signed This label is managed by the Meta Open Source bot.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants