fix(tabs): gate tab-strip scrolling on prefers-reduced-motion - #1304
Merged
Conversation
h4yfans
marked this pull request as ready for review
August 12, 2026 14:52
|
React Doctor found no new issues. 🎉 Reviewed by React Doctor for commit |
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
This was referenced Aug 12, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #1290. Part of epic #987 (memory/CPU audit 2026-08).
What was wrong
apps/desktop/src/renderer/src/components/tabs/tab-bar-with-drag.tsx:138— the effect that keeps the active tab visible always asked for an animated scroll:Per spec the
behaviormember of a scroll options dictionary overrides the CSSscroll-behaviorproperty, so none of the@media (prefers-reduced-motion: reduce)blocks inassets/base.csscan suppress it — and the global one there only clampsanimation-duration/transition-duration, which do not apply to scrolling at all. Result: a user who asked their OS to reduce motion still got an animated horizontal slide of the tab strip on every single tab activation, one of the most repeated interactions in the app.The same file had two more ungated motion paths on the same strip:
:153the chevron buttons —el.scrollBy({ left: distance * sign, behavior: 'smooth' }), same override, same reason.:222thescroll-smoothclass.handleWheelscrolls by assigningscrollLeftdirectly, and the CSS property animates that assignment — there is no options object to gate, so this one genuinely does need the CSS side.PR #1277 cut this from up to 3 smooth scrolls per activation to 1 by stabilising the effect deps; it did not add a preference gate, so the frequency improved and the a11y gap did not.
What changed
One module-scope helper, read at call time:
applied to both scroll calls, plus
motion-reduce:scroll-autoalongsidescroll-smoothfor the wheel path. Reduce on →'auto'(the tab still gets scrolled into view, it just arrives instantly); reduce off →'smooth', byte-for-byte the previous behaviour.Read at call time rather than via a subscription (
matchMedia(...)matches the existing renderer precedent incomponents/onboarding/use-first-run-tour.ts:41): nothing renders from the value, so a hook would only add a subscription and a re-render for a value used imperatively. It also means a mid-session change to the OS setting is honoured by the very next scroll — covered by a test that flips the preference without remounting.Deliberately not done:
tabVariantsReducedwas not wired up. The issue flagged it as having no component consumer — correct, but so doestabVariantsitself: nothing in the tab strip usesmotion/reactat all. Wiring the reduced variant up means first introducing framer-motion enter/exit animation to the tabs, which is a redesign, not a gate. Worth its own issue; it is not a prerequisite for this fix.tab-bar.tsx:69,74andtab-bar-with-overflow.tsx:131,151carry the identical ungatedbehavior: 'smooth'. Left alone:TabBar/TabBarWithOverflowhave no consumer outsidecomponents/tabs/index.tsand their own tests — the live surface isTabBarWithDrag. Flagging rather than touching, since "are these dead?" is a separate question from this one.How it was proven
apps/desktop/src/renderer/src/components/tabs/tab-bar-scroll.test.tsx, four new cases: the smooth default, the reduced-motion branch (assertingbehavior: 'auto'and that the correct tab element is still scrolled into view, on mount and on re-activation), the chevron gate with the preference flipped mid-session, and the CSS variant for wheel scrolling.Same test file against the pre-fix
tab-bar-with-drag.tsx(git checkout origin/main -- <source>, test file untouched):The 3 pre-existing cases pass in both runs — the gate does not change the un-reduced path or the once-per-activation behaviour from #1277.
Verification
Docs:
apps/docs/src/user-guide/tabs-split-view.mdpicks up the reduced-motion behaviour in the existing tab-bar scrolling paragraph.Backward compatibility
Renderer-only, presentation-only: no schema, IPC contract, settings, sync or file-format surface is touched, and with reduce-motion off the emitted scroll options are identical to before.