Skip to content

refactor(tablist): use useListFocus hasRovingTabIndex instead of inline repair#3484

Draft
cixzhang wants to merge 1 commit into
mainfrom
navi/refactor/tablist-use-roving
Draft

refactor(tablist): use useListFocus hasRovingTabIndex instead of inline repair#3484
cixzhang wants to merge 1 commit into
mainfrom
navi/refactor/tablist-use-roving

Conversation

@cixzhang

@cixzhang cixzhang commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Summary

Refactors TabList to fully use useListFocus's built-in roving-tabindex support (hasRovingTabIndex) instead of a hand-rolled tab-stop repair effect.

useListFocus now owns the single tab stop across the tab strip: it stamps tabindex 0/-1, repairs the stop on mount and as stops mount/unmount or toggle disabled, and — via handleFocus on the <nav> — keeps the stop in sync after clicks or programmatic focus. This natively replaces the inline useIsomorphicLayoutEffect in TabList.tsx that queried nav stops, checked whether any had tabIndex === 0, and promoted the first enabled one if not.

Changes

  • Pass hasRovingTabIndex: true to the existing useListFocus call.
  • Destructure handleFocus from the hook and wire it as onFocus on the <nav> (keeps the roving stop in sync with whatever got focus).
  • Delete the hand-rolled useIsomorphicLayoutEffect tab-stop repair block, its now-unused isDisabledStop helper, and the useIsomorphicLayoutEffect import.
  • Tab.tsx is unchanged: it keeps rendering tabIndex={isSelected ? 0 : -1} as the initial source of truth. The hook's syncTabStops (enabled.find(el => tabindex === '0') ?? enabled[0]) preserves an existing tab stop and only promotes the first enabled stop when none is tabbable — matching the deleted effect's behavior exactly, so the selected tab remains the initial tab stop.

Behavior

No behavior change. Arrow / Home / End navigation, wrapping, and disabled-skip were already handled by useListFocus; the initial-tab-stop and repair semantics are now handled natively by the hook rather than duplicated inline.

Verification

  • pnpm -F @astryxdesign/core typecheck — exit 0
  • eslint TabList.tsx Tab.tsx — clean
  • vitest run packages/core/src/TabList — 31/31 pass (no keyboard regression)
  • vitest run packages/core/src/hooks/useListFocus — 23/23 pass

@vercel

vercel Bot commented Jul 3, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
astryx Ready Ready Preview, Comment Jul 3, 2026 5:04am

Request Review

@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Meta Open Source bot. label Jul 3, 2026
@github-actions

github-actions Bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

PR Analysis Report

📚 Storybook Preview

View Storybook for this PR
GitHub Pages may take up to a minute to hydrate after deploy.

🧪 Sandbox Preview

View Sandbox for this PR
GitHub Pages may take up to a minute to hydrate after deploy.

No new or modified components detected.

Bundle Size Summary

Package Size (ESM) Size (CJS) Gzipped
@astryxdesign/core N/A 4.6KB 0B

Accessibility Audit

Status: No accessibility violations detected.


Generated by PR Enrichment workflow | Storybook | Sandbox | View full report

github-actions Bot added a commit that referenced this pull request Jul 3, 2026
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.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants