Repository navigation
A signup page's first Tab skips the header and lands on the next tab #644
Description
What happens
On a signup's pages, a keyboard user's first Tab skips the whole top of the page. Load any signup's Build tab and press Tab: focus lands on the Responses tab. Before it come the breadcrumb, Help, the account link, Preview, Publish (or Share), ⋯ and the public link's copy and open buttons, so reaching Publish takes two Shift+Tabs back. On the Responses tab, the first Tab lands on Settings. The dashboard behaves: its first Tab lands on the OpenSignup logo.
Where: TabsNav in src/components/signup/TabsNav.tsx. When the page loads and whenever the tab changes, an effect runs activeRef.current?.scrollIntoView({ inline: 'center', block: 'nearest' }). That keeps the current tab in view when the tab strip scrolls sideways on a phone. But in Chrome, scrollIntoView also moves the point where the next Tab starts, the same way a click on that element would. Focus itself stays on the page body, so nothing shows where you are. The next Tab just starts after the current tab.
Checked in Chromium: on the dashboard, scrolling one signup's link into view the same way makes the first Tab land on the link after it. Without that, the first Tab lands on the logo.
Fix, in outline
- Scroll the tab strip itself instead of asking the tab to scroll into view. Set the strip's
scrollLeftso the current tab sits in the middle, for exampletab.offsetLeft - strip.offsetLeft - (strip.clientWidth - tab.offsetWidth) / 2, with a ref on therole="tablist"element. - Tried in Chromium on a small test page at 1280 and 390 wide:
scrollLeftscrolls the strip by the same amount (160px againstscrollIntoView's 159px), and the first Tab still lands on the first link on the page.
Watch for
- The phone, where the strip scrolls. The current tab must still come into view when the page loads on a narrow screen, and when the tab changes.
- Overlap with the open stack. Check the page when Publish and share is pressed #580 adds a click handler to the tabs in
TabsNav.tsx(for a pending fix). The effect it leaves alone is the one to change, so whichever lands second has a small merge. - Other
scrollIntoViewcalls that run without the user asking would do the same. This is the one found on a page's load.
Done when
- On a signup's Build, Responses and Settings tabs, the first Tab after the page loads lands on the first link in the header, on a computer and at 390px wide.
- At 390px wide, the current tab is still scrolled into view when the page loads.
pnpm lint && pnpm typecheck && pnpm knip && pnpm testpass.
Found by the UX walkthrough (scenario X2, keyboard only) on 2026-10-25, on the #581 stack tip. Checked against the code on main at 35288fa, where the effect is the same, and confirmed by testing scrollIntoView and scrollLeft in Chromium.
Generated by Claude Code
Activity
Merge main into claude/issue-644-tabs-scroll