Skip to content

[feat] Add scroll arrows for overflowing tabs in st.tabs#13987

Merged
lukasmasuch merged 11 commits intodevelopfrom
lukasmasuch/tabs-overflow-arrows
Feb 18, 2026
Merged

[feat] Add scroll arrows for overflowing tabs in st.tabs#13987
lukasmasuch merged 11 commits intodevelopfrom
lukasmasuch/tabs-overflow-arrows

Conversation

@lukasmasuch
Copy link
Copy Markdown
Collaborator

@lukasmasuch lukasmasuch commented Feb 17, 2026

Describe your changes

  • Add clickable left/right scroll arrows that appear when tabs overflow their container horizontally
  • Arrows conditionally show based on scroll position: left arrow appears when scrolled right, right arrow appears when content overflows to the right
  • Include accessibility support with aria-labels and focus-visible styling for keyboard navigation
  • Use passive scroll event listeners for better performance

Github Issues

Testing Plan

  • Unit Tests (JS and/or Python)
  • E2E Tests

When tabs overflow their container horizontally, clickable left/right arrow
buttons appear to allow users to scroll through the tabs. The arrows
conditionally show based on scroll position.
@lukasmasuch lukasmasuch added change:feature PR contains new feature or enhancement implementation impact:users PR changes affect end users labels Feb 17, 2026
Copilot AI review requested due to automatic review settings February 17, 2026 23:22
@snyk-io
Copy link
Copy Markdown
Contributor

snyk-io bot commented Feb 17, 2026

Snyk checks have passed. No issues have been found so far.

Status Scanner Critical High Medium Low Total (0)
Open Source Security 0 0 0 0 0 issues
Licenses 0 0 0 0 0 issues

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Feb 17, 2026

✅ PR preview is ready!

Name Link
📦 Wheel file https://core-previews.s3-us-west-2.amazonaws.com/pr-13987/streamlit-1.54.0-py3-none-any.whl
📦 @streamlit/component-v2-lib Download from artifacts
🕹️ Preview app pr-13987.streamlit.app (☁️ Deploy here if not accessible)

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR adds horizontal scroll arrows to st.tabs that appear when tabs overflow their container. The arrows are interactive buttons positioned absolutely over the tab list, using a gradient background to fade into the content. The implementation includes proper accessibility features (aria-labels, focus-visible styling) and uses passive scroll listeners for performance.

Changes:

  • Adds left/right scroll arrow buttons that conditionally render based on scroll position
  • Removes the static gradient fade effect previously used for overflow indication
  • Implements scroll state tracking with event listeners and callback hooks
  • Adds E2E tests for scroll arrow behavior and visual snapshots

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 5 comments.

File Description
frontend/lib/src/components/elements/Tabs/styled-components.ts Removed static gradient fade effect and added new StyledScrollArrow button component with gradient backgrounds and accessibility styling
frontend/lib/src/components/elements/Tabs/Tabs.tsx Implemented scroll state management with hooks, added scroll event listener, and rendered conditional scroll arrow buttons with click handlers
frontend/lib/src/components/elements/Tabs/Tabs.test.tsx Added basic unit test verifying scroll arrows don't appear when tabs don't overflow
e2e_playwright/st_tabs_test.py Added functional test for scroll arrow interaction and snapshot test for visual appearance in light/dark themes

@lukasmasuch
Copy link
Copy Markdown
Collaborator Author

@cursor review

Copy link
Copy Markdown

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.

- Add ResizeObserver to update scroll arrows on container resize
- Use theme.shadows.focusRing for consistent focus styling
- Replace hardcoded timeout with Playwright expect() assertions
Add wait_for_timeout(500) after clicking the scroll arrow to allow
the smooth scroll animation to complete before taking the snapshot.
The scroll uses behavior: "smooth" which animates over ~200-400ms,
and without waiting, the snapshot captures inconsistent scroll
positions leading to 2-5% pixel differences across browsers/themes.
@lukasmasuch lukasmasuch added ai-review If applied to PR or issue will run AI review workflow flaky-verify If applied to PR will run flaky test verification workflow labels Feb 18, 2026
@github-actions github-actions bot removed flaky-verify If applied to PR will run flaky test verification workflow ai-review If applied to PR or issue will run AI review workflow labels Feb 18, 2026
@github-actions
Copy link
Copy Markdown
Contributor

Summary

This PR adds clickable left/right scroll arrows to st.tabs when tabs overflow their container horizontally. The old passive gradient overlay (::after pseudo-element on StyledTabContainer) is replaced with interactive StyledScrollArrow buttons that include a gradient background, chevron icons, and smooth-scroll behavior. A ResizeObserver and scroll event listener keep the arrow visibility in sync with the actual scroll position. The feature includes E2E snapshot tests across browsers/themes and a frontend unit test for the no-overflow case.

Code Quality

Good patterns observed:

  • Constants SCROLL_AMOUNT and SCROLL_TOLERANCE are correctly extracted to module-level scope (Tabs.tsx lines 41–42).
  • useCallback is used for updateScrollState, scroll, handleScrollLeft, and handleScrollRight to maintain referential stability and prevent unnecessary effect re-runs.
  • The scroll/resize effect (lines 112–129) has proper cleanup: both removeEventListener and resizeObserver.disconnect() are called in the cleanup function.
  • The scroll event listener uses { passive: true } for performance.
  • StyledScrollArrow is a styled.button — semantic HTML for interactive elements, following the accessibility guidelines in AGENTS.md.
  • data-testid values follow the stComponentSubcomponent naming convention (stTabsScrollLeft, stTabsScrollRight).

Minor observations:

  • The second useEffect (lines 131–145) mixes two concerns: calling updateScrollState() and reconciling the active tab. This is pre-existing behavior inherited from the original code and not a regression introduced by this PR, but could be improved in a follow-up.
  • The fixed SCROLL_AMOUNT = 200 uses pixels, which is fine for scroll behavior (not visual styling) but could alternatively scale with container width for a more adaptive feel. This is a design choice, not a bug.
  • The existing last-tab padding hack (lines 280–284: paddingRight: calc(${theme.spacing.lg} * 0.6)) is retained, which originally existed to keep content from being hidden behind the gradient. With the new arrow width being theme.spacing.xl, the padding may need revisiting if the last tab is visually clipped behind the arrow — but this should be caught by the E2E snapshots.

Test Coverage

Frontend unit test (Tabs.test.tsx):

  • Adds a negative test verifying scroll arrows are not rendered when tabs don't overflow. This is appropriate given JSDOM doesn't implement actual scroll dimensions, so overflow can't be simulated in unit tests.
  • Uses the correct queryByTestId + not.toBeInTheDocument() pattern per the RTL best practices in AGENTS.md.

E2E tests (st_tabs_test.py):

  • test_overflow_scroll_arrows: Tests the full interactive flow — verifies right arrow is visible initially, left arrow is hidden, scrolls right to reveal left arrow, scrolls back to hide left arrow. Good use of Playwright's auto-waiting expect assertions.
  • test_overflow_scroll_arrows_snapshot: Visual regression snapshots for both "right arrow only" and "both arrows" states, across light/dark themes and 3 browser engines (18 new snapshot images total).
  • The wait_for_timeout(500) on line 123 is used to wait for smooth scroll animation to complete before taking a snapshot. The AGENTS.md discourages wait_for_timeout but acknowledges it's acceptable "when there is a specific purpose" — this qualifies, and the comment explains the rationale. That said, this could be fragile on slow CI runners; using wait_until to check that scrollLeft has stabilized would be more robust but harder to implement from Playwright.
  • The existing test_tabs_render_correctly snapshot (st_tabs-fixed_width) is updated (6 changed snapshot images), which is expected since StyledTabContainer no longer applies the ::after gradient.

Coverage gaps (minor):

  • No test for ResizeObserver behavior (e.g., arrows appearing/disappearing when the container resizes). This is hard to test in both JSDOM and E2E but worth noting.
  • No keyboard navigation test for the scroll arrows (e.g., tabbing to arrow buttons and pressing Enter/Space).

Backwards Compatibility

No breaking changes. The modifications are purely frontend and internal:

  • tabHeight was removed from StyledTabContainerProps, but this is an internal styled component, not part of the public API.
  • The ::after gradient overlay is replaced by interactive arrow buttons — a visual enhancement, not a regression.
  • No protobuf or Python API changes.
  • The underlying st.tabs Python API is unchanged.

Security & Risk

No security concerns. This is a self-contained frontend UI enhancement with no new network calls, no data handling changes, and no new dependencies (the @emotion-icons/material-outlined and Icon component were already available in the codebase).

Risk assessment: Low. The change only affects visual presentation and scroll interaction of the tab bar. The ResizeObserver and scroll listener are well-guarded with cleanup. The worst-case failure mode is arrows not appearing/disappearing at the right time, which would be a visual bug, not a functional regression.

Accessibility

  • Semantic HTML: StyledScrollArrow renders as a <button>, which is the correct element for click interactions.
  • Accessible names: Both arrows have descriptive aria-label attributes ("Scroll tabs left" / "Scroll tabs right").
  • Focus styling: The &:focus rule removes the native outline, and &:focus-visible replaces it with theme.shadows.focusRing. This follows the AGENTS.md guidance: "Don't remove focus outlines without replacing them. Prefer :focus-visible styles and use theme.shadows values for consistent rings."
  • No duplicate tab stops: The arrows are simple buttons and don't create wrapper-level tab stop issues.
  • Icon treatment: The Icon component wraps the chevron SVGs, which is consistent with the rest of the codebase.

Recommendations

  1. Consider debouncing or throttling updateScrollState: The scroll event fires at high frequency during smooth scrolling. While React 18 batches the three setState calls, a requestAnimationFrame throttle on the scroll handler would reduce unnecessary work. This is optional and not blocking — the current approach works fine for the tab bar size.

  2. Consider making the wait_for_timeout(500) in the E2E snapshot test more robust: If CI runners are slow, 500ms may not be enough for smooth scroll to complete. A wait_until approach checking the scroll position could be more reliable, though harder to implement from Playwright. Alternatively, the scroll animation could be disabled for testing.

  3. Minor: the SCROLL_AMOUNT could be relative (e.g., a percentage of the container width) for a more adaptive scrolling feel on very wide or very narrow containers. This is a UX polish suggestion, not a functional issue.

Verdict

APPROVED: Well-implemented feature with proper React patterns, good accessibility support, comprehensive E2E snapshots, and no backwards compatibility concerns. The minor suggestions above are optional improvements that can be addressed in follow-up work.


This is an automated AI review by opus-4.6-thinking.

@github-actions
Copy link
Copy Markdown
Contributor

Consolidated Code Review: PR #13987 — [feat] Add scroll arrows for overflowing tabs in st.tabs

Summary

This PR adds interactive left/right scroll arrow buttons to st.tabs when tabs overflow their container horizontally. Previously, overflowing tabs only displayed a static right-side gradient fade with no way to scroll. Now, arrows conditionally appear based on scroll position: a right arrow when content overflows to the right, and a left arrow when scrolled away from the start. The implementation is purely frontend — no backend or protobuf changes.

Changed files (22):

  • frontend/lib/src/components/elements/Tabs/Tabs.tsx — Core logic: scroll state tracking via useState/useCallback, event listeners, ResizeObserver, arrow rendering
  • frontend/lib/src/components/elements/Tabs/styled-components.ts — Replaced ::after pseudo-element gradient with StyledScrollArrow button component; simplified StyledTabContainer
  • frontend/lib/src/components/elements/Tabs/Tabs.test.tsx — Unit test for non-overflow case
  • e2e_playwright/st_tabs_test.py — E2E tests for arrow visibility, interaction, and themed snapshots
  • 18 snapshot image files (12 new, 6 updated)

Code Quality

Both reviewers agreed the code is well-structured and follows existing codebase patterns. Key strengths identified:

  • useCallback is used for updateScrollState, scroll, handleScrollLeft, and handleScrollRight — ensuring referential stability and preventing unnecessary effect re-runs/re-renders.
  • The scroll event listener uses { passive: true } for better scroll performance.
  • ResizeObserver handles container resize (window resize, sidebar toggle) with proper cleanup on unmount.
  • Constants (SCROLL_AMOUNT, SCROLL_TOLERANCE) are appropriately extracted to module scope.
  • StyledTabContainer was cleanly simplified by removing the tabHeight prop and ::after pseudo-element.

Minor observations (non-blocking):

  • The second useEffect (lines ~131–145) mixes two concerns: calling updateScrollState() and reconciling the active tab key when node.children.length changes. This is largely pre-existing technical debt (the existing eslint-disable TODO comment acknowledges this). Adding updateScrollState here is justified since ResizeObserver may not fire when tabs are added/removed if only scrollWidth changes.
  • The scroll event fires frequently during smooth scroll animations, triggering three setState calls each time. While React 18 batches these automatically, a lightweight requestAnimationFrame throttle could reduce updates further. This is a minor optimization, not blocking.

Test Coverage

Both reviewers agreed coverage is good and well-aligned with the changes.

Frontend unit test (Tabs.test.tsx):

  • Adds a negative test verifying scroll arrows are not rendered when tabs don't overflow. This is the correct approach given JSDOM's limitation of not implementing actual scrolling/layout. Uses proper queryByTestId + not.toBeInTheDocument() pattern.

E2E tests (st_tabs_test.py):

  • test_overflow_scroll_arrows: Functional behavior test — right arrow visible initially, left arrow hidden, clicking right reveals left, clicking left back to start hides left. Uses Playwright's auto-wait assertions.
  • test_overflow_scroll_arrows_snapshot: Visual regression test in both light and dark themes, capturing "right arrow only" and "both arrows" states across all browsers (chromium, firefox, webkit).

Both reviewers flagged the wait_for_timeout(500) at line 122 of st_tabs_test.py as a minor flakiness risk. The 500ms is reasonable for typical smooth scroll durations (200–400ms), but condition-based waiting would be more robust. This is non-blocking but worth monitoring.

Coverage gap noted (non-blocking): The frontend unit suite does not directly validate overflow transition logic (canScrollLeft/canScrollRight) because JSDOM doesn't model layout/scroll. This is covered at the E2E level. A mocked frontend test for arrow state transitions could improve coverage but is not required.

Backwards Compatibility

Both reviewers agreed — no breaking changes:

  • No changes to the st.tabs() Python API or protobuf definitions.
  • The visual change (static gradient → interactive arrows) is an intentional UX improvement.
  • The removed tabHeight from StyledTabContainerProps is purely internal.

Security & Risk

Both reviewers agreed — no security concerns. Changes are purely frontend UI with no new data handling, network requests, or user input processing. Regression risk is low and mitigated by the added test coverage.

Edge case noted: Rapid clicking of scroll arrows may queue multiple smooth scrolls. Browsers natively handle this by interrupting previous smooth scrolls, so this is a non-issue.

Accessibility

Both reviewers agreed the accessibility implementation is strong:

  • Semantic <button> elements for scroll arrows (not div + onClick).
  • Descriptive aria-label attributes: "Scroll tabs left" / "Scroll tabs right".
  • &:focus-visible with theme.shadows.focusRing for keyboard focus indication.
  • Native keyboard accessibility (Enter/Space to activate, Tab to focus).

Additional observation (one reviewer): The scroll arrow buttons appear as separate tab stops in the keyboard navigation flow, independent of the tab list's internal arrow key navigation. This is standard behavior for scroll controls and acceptable.

Recommendations

  1. Minor (test robustness): Consider replacing wait_for_timeout(500) in st_tabs_test.py with a condition-based wait to reduce flaky timing dependency on CI.
  2. Minor (performance): Consider throttling updateScrollState via requestAnimationFrame to reduce state updates during smooth scroll animations.
  3. Minor (test coverage): Consider adding a mocked frontend unit test that verifies arrow state transitions by mocking scroll metrics/events.

None of these are blocking.

Verdict

APPROVED — Both reviewers approved this PR. The feature is well-implemented with clean code, proper accessibility, good use of React hooks and DOM APIs, and thorough test coverage across unit and E2E tests. No blocking issues were identified.


Consolidated review by opus-4.6-thinking. Individual reviews from 2/2 expected models received.


📋 Review by `gpt-5.3-codex-high`

Summary

This PR adds horizontal overflow controls to st.tabs by introducing left/right scroll arrow buttons, dynamic overflow detection, and updated visuals for tab overflow states. It also adds frontend unit coverage for the non-overflow case and extends E2E tests/snapshots to validate arrow visibility and interaction across themes/browsers.

Code Quality

Implementation quality is strong overall for this scope: the feature is encapsulated in Tabs and associated styled components, uses semantic controls (button), and includes proper cleanup for listeners/observers in frontend/lib/src/components/elements/Tabs/Tabs.tsx.

I did not find blocking logic defects in the changed production code. One non-blocking maintainability/testing concern:

  • e2e_playwright/st_tabs_test.py:122 uses a fixed wait_for_timeout(500); this can increase flakiness compared with condition-based waiting.

Test Coverage

Coverage is generally good and aligned with the change:

  • Frontend unit test added for the no-overflow state in frontend/lib/src/components/elements/Tabs/Tabs.test.tsx.
  • E2E behavior test added for arrow visibility + click interaction in e2e_playwright/st_tabs_test.py.
  • New themed snapshots added for right-arrow and both-arrows states, plus updated fixed-width snapshots.

Remaining non-blocking gap:

  • The frontend unit suite does not directly validate overflow transition logic (canScrollLeft / canScrollRight) because JSDOM does not natively model layout/scroll; this is currently covered only at E2E level.

Backwards Compatibility

No API or protobuf changes were introduced. Behavior changes are additive at the UI layer (new overflow controls), so this appears backwards compatible for existing st.tabs usage.

Security & Risk

No direct security concerns identified. Regression risk is low-to-moderate and primarily UI/state-sync related (scroll event timing and animation timing), mitigated by the added E2E assertions and snapshots.

Accessibility

Accessibility handling is good for the new controls:

  • Scroll controls are semantic button elements (frontend/lib/src/components/elements/Tabs/styled-components.ts).
  • Icon-only buttons have explicit accessible names via aria-label (frontend/lib/src/components/elements/Tabs/Tabs.tsx).
  • Keyboard focus styling is provided with :focus-visible (frontend/lib/src/components/elements/Tabs/styled-components.ts).

Recommendations

  1. Replace the fixed sleep at e2e_playwright/st_tabs_test.py:122 with a condition-based wait (or a helper that waits for scroll settling) to reduce flaky timing dependency.
  2. Consider adding a targeted frontend test that mocks scroll metrics/events to verify arrow state transitions (left appears after right scroll and hides again at origin) at unit level.

Verdict

APPROVED: The feature is well-implemented, accessible, and sufficiently covered for merge, with only minor non-blocking test robustness improvements suggested.


This is an automated AI review by gpt-5.3-codex-high. Please verify the feedback and use your judgment.

📋 Review by `opus-4.6-thinking`

Summary

This PR adds clickable left/right scroll arrows to st.tabs when tabs overflow their container horizontally. Previously, overflowing tabs only had a right-side gradient fade with no interactive way to scroll. The arrows now conditionally appear based on scroll position: a right arrow when content overflows to the right, and a left arrow when scrolled away from the start. The implementation is purely frontend (no backend/protobuf changes).

Changed files:

  • frontend/lib/src/components/elements/Tabs/Tabs.tsx — Core logic: scroll state tracking, event listeners, ResizeObserver, arrow rendering
  • frontend/lib/src/components/elements/Tabs/styled-components.ts — Replaced ::after pseudo-element gradient with a StyledScrollArrow button component
  • frontend/lib/src/components/elements/Tabs/Tabs.test.tsx — Unit test for non-overflow case
  • e2e_playwright/st_tabs_test.py — E2E tests for arrow visibility, interaction, and snapshots
  • 18 snapshot image files (new and updated)

Code Quality

The code is well-structured and follows existing codebase patterns.

Strengths:

  • useCallback is used for updateScrollState, scroll, handleScrollLeft, and handleScrollRight to ensure referential stability, preventing unnecessary effect re-runs and re-renders — consistent with the React best practices documented in frontend/AGENTS.md.
  • The scroll event listener is registered with { passive: true } for better scroll performance.
  • ResizeObserver is used to re-evaluate overflow state on container resize (e.g., window resize, sidebar toggle), with proper cleanup.
  • Constants (SCROLL_AMOUNT, SCROLL_TOLERANCE) are correctly extracted to module-level scope.
  • The StyledScrollArrow uses a semantic <button> element (not a <div> with onClick), following the accessibility guidelines.

Minor observations:

  • In Tabs.tsx lines 131–145, the second useEffect mixes two concerns: calling updateScrollState() and reconciling the active tab key when node.children.length changes. While this is largely pre-existing technical debt (noted by the existing eslint-disable TODO comment), adding updateScrollState to this effect is justified because ResizeObserver may not fire when tabs are added/removed (if clientWidth doesn't change, only scrollWidth does).
  • The SCROLL_AMOUNT = 200 uses a pixel value, but this is correct since the scrollBy DOM API requires pixel values — not a styling concern.
  • The StyledTabContainer was cleanly simplified by removing the tabHeight prop and ::after pseudo-element, moving the gradient responsibility to the arrow buttons themselves.

Test Coverage

Frontend unit test (Tabs.test.tsx):

  • Adds a negative test verifying scroll arrows are not rendered when tabs don't overflow. This is the correct approach given JSDOM's limitation of not implementing actual scrolling/layout. The test uses the proper queryByTestId + not.toBeInTheDocument() pattern per RTL best practices.

E2E tests (st_tabs_test.py):

  • test_overflow_scroll_arrows: Tests functional behavior — right arrow visible initially, left arrow hidden, clicking right arrow reveals left arrow, clicking left arrow back to start hides left arrow. Good use of Playwright's expect auto-wait assertions.
  • test_overflow_scroll_arrows_snapshot: Visual regression tests in both light and dark themes (via themed_app), capturing both "right arrow only" and "both arrows" states. The wait_for_timeout(500) usage is justified here (documented reason: waiting for CSS smooth scroll animation completion before snapshot), though it could theoretically be fragile on slow CI runners.
  • Existing snapshot images for st_tabs-fixed_width are updated, which is expected since the ::after gradient pseudo-element was removed from StyledTabContainer.

Coverage is good. Both the functional and visual aspects of the new feature are tested across browsers and themes.

Backwards Compatibility

  • Public API unchanged: No changes to st.tabs() Python API or protobuf definitions.
  • Visual change: The static right-side gradient fade is replaced by interactive scroll arrows with gradient backgrounds. This is an intentional UX improvement, not a regression.
  • Internal interface change: tabHeight was removed from StyledTabContainerProps — this is only used internally within the Tabs component, not exposed to consumers.
  • No breaking changes.

Security & Risk

  • No security concerns. Changes are purely frontend UI with no new data handling, network requests, or user input processing.
  • Low regression risk: The core tab selection logic is unchanged. The scroll state tracking is additive. The existing isOverflowing state is now computed in updateScrollState instead of directly in the old effect, but the logic is equivalent.
  • Potential edge case: If a user has many tabs and rapidly clicks the scroll arrows, multiple smooth scrolls may queue up. The scrollBy with behavior: "smooth" handles this natively (browsers typically interrupt the previous smooth scroll), so this should be fine.

Accessibility

The implementation follows accessibility best practices documented in frontend/AGENTS.md:

  • Semantic HTML: Uses <button> elements for the scroll arrows (not div + onClick).
  • Accessible names: Both arrows have descriptive aria-label attributes ("Scroll tabs left" / "Scroll tabs right").
  • Focus styling: Uses &:focus-visible with theme.shadows.focusRing for keyboard focus indication. The &:focus { outline: "none" } is consistent with the codebase convention (browsers that support :focus-visible are assumed).
  • Keyboard navigation: Buttons are natively keyboard-accessible (Enter/Space to activate, Tab to focus).

Recommendations

  1. Consider debouncing/throttling updateScrollState: The scroll event fires very frequently during smooth scroll animations, triggering three setState calls each time. While React 18 batches these, a lightweight throttle (e.g., via requestAnimationFrame) could reduce the number of state updates during scroll. This is a minor performance optimization and not blocking.

  2. E2E wait_for_timeout(500) robustness: In test_overflow_scroll_arrows_snapshot (line 122), consider whether the assertions before the snapshot (expect(left_arrow).to_be_visible() and expect(right_arrow).to_be_visible()) plus a small additional buffer are sufficient, or if a slightly higher timeout would be warranted for slow CI. The current 500ms is reasonable for typical smooth scroll durations (200–400ms) but worth monitoring for flakiness.

  3. Minor: Arrow buttons and tab keyboard navigation interaction: When a user is navigating tabs with arrow keys (via baseui's activateOnFocus), the scroll arrow buttons are separate from the tab list's keyboard navigation. Users tabbing through the page will encounter the arrow buttons as separate tab stops. This is standard behavior for scroll controls and should be fine, but it's worth noting that screen reader users will encounter these buttons in the tab order.

Verdict

APPROVED: Well-implemented feature with proper accessibility, clean code following existing patterns, appropriate use of React hooks and DOM APIs, and good test coverage across unit and E2E tests.


This is an automated AI review by opus-4.6-thinking.

className="stTabs"
data-testid="stTabs"
isOverflowing={isOverflowing}
tabHeight={TAB_HEIGHT}
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

This was needed for the previous gradient indicator

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

question: is the back arrow intended to be a different color?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

I guess the right arrow is hovered, that's why it has a lighter color compared to the left one. I will add a reset for the hovering.

… for regeneration

The reset_hovering call ensures the hover state is cleared before taking
the snapshot, which prevents flaky test results when arrow buttons retain
hover effects from the preceding click interaction.
Resolve merge conflicts by combining:
- Tabs overflow scroll arrows (this branch)
- Dynamic tabs widget state tracking (develop)
CI-generated snapshots for the tabs overflow arrow tests
after reset_hovering fix.
Comment on lines 324 to 330
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

suggestion: Perhaps we can now remove this padding?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Removed 👍

@@ -86,6 +92,34 @@ function Tabs(props: Readonly<TabProps>): ReactElement {
const theme = useEmotionTheme()

const [isOverflowing, setIsOverflowing] = useState(false)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

suggestion: I think we can now simplify isOverflowing = canScrollLeft || canScrollRight

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Changed 👍

Copy link
Copy Markdown
Collaborator

@mayagbarnes mayagbarnes left a comment

Choose a reason for hiding this comment

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

Couple suggestions along with the graphite comment (re: SCROLL_TOLERANCE) , but overall LGTM!

- Use SCROLL_TOLERANCE for left scroll check consistency (fixes
  potential flickering near left edge from floating-point rounding)
- Derive isOverflowing from canScrollLeft || canScrollRight instead
  of tracking as separate state
- Remove extra padding on last tab (no longer needed with interactive
  scroll arrows)
@lukasmasuch lukasmasuch enabled auto-merge (squash) February 18, 2026 21:27
@lukasmasuch lukasmasuch merged commit 80f7918 into develop Feb 18, 2026
43 checks passed
@lukasmasuch lukasmasuch deleted the lukasmasuch/tabs-overflow-arrows branch February 18, 2026 21:43
lukasmasuch added a commit that referenced this pull request Feb 20, 2026
## Describe your changes

- Add clickable left/right scroll arrows that appear when tabs overflow
their container horizontally
- Arrows conditionally show based on scroll position: left arrow appears
when scrolled right, right arrow appears when content overflows to the
right
- Include accessibility support with aria-labels and focus-visible
styling for keyboard navigation
- Use passive scroll event listeners for better performance

## Github Issues

- Closes #5552

## Testing Plan

- [x] Unit Tests (JS and/or Python)
- [x] E2E Tests
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

change:feature PR contains new feature or enhancement implementation impact:users PR changes affect end users

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Tabs hidden when titles exceed the content area width (no ability to scroll)

3 participants