Skip to content

fix(button-group): keep segmented control hover borders visually consistent#14067

Merged
sfc-gh-bnisco merged 1 commit intodevelopfrom
02-20-fix_button-group_keep_segmented_control_hover_borders_visually_consistent
Feb 24, 2026
Merged

fix(button-group): keep segmented control hover borders visually consistent#14067
sfc-gh-bnisco merged 1 commit intodevelopfrom
02-20-fix_button-group_keep_segmented_control_hover_borders_visually_consistent

Conversation

@sfc-gh-bnisco
Copy link
Copy Markdown
Collaborator

@sfc-gh-bnisco sfc-gh-bnisco commented Feb 23, 2026

Describe your changes

Fixed border rendering issue in segmented control components where hovering over buttons would create visual artifacts due to overlapping borders. Implemented a z-index based layering system where active and interactive (hover/focus) buttons are treated as "raised" and take ownership of shared borders with neutral neighbors. This prevents double-width border seams while maintaining proper visual hierarchy.

Added comprehensive E2E tests with visual regression snapshots to verify hover states work correctly for both single and multi-selection modes, including edge cases where hovered buttons are adjacent to selected buttons.

Screenshot or video (only for visual changes)

See snapshot updates!

GitHub Issue Link (if applicable)

Fixes #12802

Testing Plan

  • E2E Tests: Added visual regression tests for hover border behavior in both single and multi-selection segmented controls, including active-adjacent hover states

Contribution License Agreement

By submitting this pull request you agree that all contributions to this project are made under the Apache 2.0 license.

@snyk-io
Copy link
Copy Markdown
Contributor

snyk-io bot commented Feb 23, 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 23, 2026

✅ PR preview is ready!

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

Copy link
Copy Markdown
Collaborator Author

This stack of pull requests is managed by Graphite. Learn more about stacking.

@sfc-gh-bnisco sfc-gh-bnisco added change:bugfix PR contains bug fix implementation impact:users PR changes affect end users security-assessment-completed feature:st.button Related to the `st.button` widget labels Feb 23, 2026 — with Graphite App
@sfc-gh-bnisco sfc-gh-bnisco force-pushed the 02-20-fix_button-group_keep_segmented_control_hover_borders_visually_consistent branch from 153dd0b to 3f40d32 Compare February 23, 2026 03:19
@sfc-gh-bnisco sfc-gh-bnisco added the ai-review If applied to PR or issue will run AI review workflow label Feb 23, 2026
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

Fixes a visual border artifact in st.segmented_control caused by overlapping semi-transparent borders on hover by introducing a border “ownership” model (raised active/interactive segments + hiding shared borders on neighbors). Adds E2E visual regression coverage for hover states (including active-adjacent hover) and updates snapshots impacted by the styling change.

Changes:

  • Adjust segmented control button styling to raise active/hovered segments and avoid double-width border seams by hiding shared borders on adjacent segments.
  • Add a new E2E snapshot test + app cases covering hover border behavior for both multi- and single-selection (including hover next to a selected segment).
  • Update/add Playwright snapshot images affected by the new border rendering.

Reviewed changes

Copilot reviewed 3 out of 25 changed files in this pull request and generated no comments.

File Description
frontend/lib/src/components/shared/BaseButton/styled-components.ts Implements the segmented control border layering/ownership logic using z-index + adjacency selectors.
e2e_playwright/st_segmented_control_test.py Adds hover-border snapshot regression test (multi + single selected-adjacent) and a small helper.
e2e_playwright/st_segmented_control.py Adds new “Hover border regression” section/widgets used by the new E2E test.
e2e_playwright/snapshots/linux/st_segmented_control_test/*.png Updates existing snapshots and adds new hover-border snapshots reflecting the new styling.

@github-actions github-actions bot removed the ai-review If applied to PR or issue will run AI review workflow label Feb 23, 2026
@github-actions
Copy link
Copy Markdown
Contributor

Summary

This PR fixes a visual border rendering issue in segmented control components (issue #12802). When hovering over buttons in a segmented control, overlapping borders caused double-width seam artifacts. The fix introduces a z-index-based layering system with CSS border-color transparency rules so that "raised" buttons (active or hovered/focused) own shared borders with their neutral neighbors, preventing visual artifacts.

Changes:

  • frontend/lib/src/components/shared/BaseButton/styled-components.ts: Added module-level CSS selector constants and new CSS rules implementing explicit border ownership in StyledSegmentedControlButton.
  • e2e_playwright/st_segmented_control.py: Added hover border regression test scenarios (multi-select and single-select with adjacent active button).
  • e2e_playwright/st_segmented_control_test.py: Added test_hovered_segmented_control_border_regression_snapshot with a reusable helper _assert_hover_border_snapshot.
  • Snapshots: 12 new snapshots for hover border regression (light/dark, 3 browsers, 2 scenarios) and 8 updated existing snapshots reflecting the CSS fix.

Code Quality

Both reviewers agreed the CSS implementation is well-structured and maintainable:

  1. Selector constants (SEGMENTED_CONTROL_ANY_ENABLED, etc.) are correctly extracted to module-level scope, consistent with the project's static data structure guidelines.

  2. Border ownership logic is sound and handles all adjacency cases:

    • Active + Any: Active always owns the shared border (strongest precedence).
    • Hover/Focus + Neutral: The interactive button owns the shared border.
    • Active + Hover: Active wins — hover rules intentionally target only neutral neighbors, avoiding conflicts.
    • Neutral + Neutral: No transparency rules apply; both keep borders.
  3. Comments explain the non-obvious "why" (border ownership model, precedence rules) rather than narrating code — appropriate for this kind of CSS complexity.

  4. The kind prop is passed directly to the DOM <button> element via BaseButton.tsx, so the attribute selectors (button[kind='segmented_control']) correctly match rendered elements.

  5. The CSS :has() pseudo-class is used for forward-looking sibling matching. Browser support is solid (Chrome 105+, Firefox 121+, Safari 15.4+, Edge 105+), consistent with the project's assumption that modern browser features are available.

  6. Minor observation: The existing &:focus-visible z-index rule (lines 371-374) is now a subset of the new compound selector (lines 346-350) for enabled buttons, but keeping it is harmless and adds clarity. The zIndex in StyledSegmentedControlButtonActive (line 385) is also pre-existing redundancy — not introduced by this PR.

No blocking code-quality issues were identified by either reviewer.

Test Coverage

Both reviewers agreed the E2E test coverage is strong:

  • Two well-chosen scenarios: (1) hover on a neutral-only group (multi-select, nothing selected) and (2) hover adjacent to a selected/active button (single-select with "North" selected, hovering "East").
  • The reusable helper _assert_hover_border_snapshot includes a negative regression check verifying that hover does not change widget state.
  • Tests follow E2E best practices: key-based locators, screenshotting specific elements, descriptive snapshot names, and coverage across 3 browsers + 2 themes (12 total new snapshots).

No frontend unit tests were added, which both reviewers considered acceptable since the changes are purely CSS styling rules in Emotion styled components — visual CSS behavior is best verified through E2E visual regression snapshots.

Residual gap (both reviewers noted): The CSS logic also handles :focus-visible but no explicit keyboard-focus snapshot was added. This is a minor gap, not a blocker.

Backwards Compatibility

Both reviewers agreed: No concerns. This is a CSS-only visual fix with no API changes, no protobuf changes, and no backend changes. Only affects segmented control button visual rendering. The updated existing snapshots confirm the fix applies consistently.

Security & Risk

Both reviewers agreed: No security concerns. Regression risk is low — the fix is scoped to segmented control buttons only, border transparency rules are carefully gated by specificity, and the z-index value used matches the pre-existing focus-visible z-index.

Accessibility

Both reviewers agreed: The changes maintain good accessibility:

  • :focus-visible is handled alongside :hover in the z-index elevation rule, ensuring keyboard-focused buttons are visually raised.
  • The pre-existing &:focus-visible z-index rule is preserved as an additional safeguard.
  • No ARIA attributes are modified; focus rings and focus behavior are unaffected.

Recommendations

No blocking issues. Non-blocking suggestions from the reviewers:

  1. Add a keyboard-driven :focus-visible snapshot to complement hover coverage and lock in border behavior for non-pointer users. (Both reviewers)
  2. Consider testing edge buttons (first/last) under hover to verify border-radius + border-transparency interaction, though the current multi-browser/theme coverage provides good confidence already. (opus-4.6-thinking)
  3. Pre-existing z-index redundancy: StyledSegmentedControlButtonActive (line 385) sets zIndex: theme.zIndices.priority which is now also covered by the new compound selector rule (lines 346-350). A follow-up cleanup could remove the redundant declaration. (opus-4.6-thinking)

Verdict

APPROVED: Both reviewers approved. The fix is well-crafted with a clear border ownership model, comprehensive visual regression tests across themes and browsers, and no backwards compatibility, security, or accessibility concerns. The non-blocking suggestions above can be addressed in follow-up PRs if desired.


This is a consolidated AI review by opus-4.6-thinking.
Individual reviews from 2 of 2 expected models. All expected models completed their reviews.


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

Summary

This PR fixes segmented-control hover border artifacts by introducing explicit shared-border ownership and stacking behavior in button styles, and it adds targeted E2E visual regression coverage for the affected hover cases. The implementation aligns with the linked issue and keeps the change scope focused on styling plus regression tests.

Code Quality

The frontend change is well-structured and maintainable: selector constants and inline comments clearly document the border-ownership model, including active precedence and neutral-neighbor hover/focus handling (frontend/lib/src/components/shared/BaseButton/styled-components.ts:313-370).
No blocking code-quality issues were identified.

Test Coverage

Coverage is strong for the reported bug:

  • New app fixtures were added for both multi-select and active-adjacent single-select hover scenarios (e2e_playwright/st_segmented_control.py:159-202).
  • A reusable helper performs a visual assertion and a negative regression check that hover does not mutate selection (e2e_playwright/st_segmented_control_test.py:42-58).
  • A dedicated snapshot test covers both new scenarios (e2e_playwright/st_segmented_control_test.py:125-147), with snapshots added across light/dark themes and major browsers.

Residual gap: the frontend logic also treats :focus-visible as interactive (frontend/lib/src/components/shared/BaseButton/styled-components.ts:321-367), but no explicit keyboard-focus snapshot was added.

Backwards Compatibility

No API or behavior-breaking changes were introduced for Python or widget contracts. This is a visual/styling fix plus test updates, so backward compatibility impact is low.

Security & Risk

No security concerns were identified.
Regression risk is low-to-moderate and mainly visual (complex sibling selector precedence and layering), but the newly added E2E snapshots materially reduce that risk.

Accessibility

Accessibility impact is positive/neutral:

  • The patch explicitly includes :focus-visible in the interactive layering model and preserves raised stacking for focused controls (frontend/lib/src/components/shared/BaseButton/styled-components.ts:346-374).
  • No semantic or ARIA regressions were introduced.

Recommendations

  1. Add one keyboard-driven :focus-visible segmented-control snapshot to complement hover coverage and lock in border behavior for non-pointer users.

Verdict

APPROVED: The fix is well-scoped, addresses the root visual defect, and is backed by solid E2E regression coverage with no blocking issues found.


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 fixes a visual border rendering issue in segmented control components (issue #12802). When hovering over buttons in a segmented control, overlapping borders caused double-width seam artifacts. The fix introduces a z-index-based layering system with CSS border-color transparency rules so that "raised" buttons (active or hovered/focused) own shared borders with their neutral neighbors, preventing visual artifacts.

Changes:

  • frontend/lib/src/components/shared/BaseButton/styled-components.ts: Added module-level CSS selector constants and new CSS rules for border ownership in StyledSegmentedControlButton.
  • e2e_playwright/st_segmented_control.py: Added hover border regression test scenarios (multi-select and single-select with adjacent active button).
  • e2e_playwright/st_segmented_control_test.py: Added test_hovered_segmented_control_border_regression_snapshot with a reusable helper function.
  • Snapshots: 12 new snapshots for hover border regression (light/dark, 3 browsers, 2 scenarios) and 8 updated existing snapshots reflecting the CSS fix.

Code Quality

The CSS implementation is well-structured and follows a clear border ownership model:

  1. Selector constants (SEGMENTED_CONTROL_ANY_ENABLED, etc.) are correctly extracted to module-level scope, following the project's static data structure guidelines (frontend/AGENTS.md).

  2. Border ownership logic is sound and handles all adjacency cases correctly:

    • Active + Any: Active always owns the shared border (strongest precedence).
    • Hover/Focus + Neutral: The interactive button owns the shared border.
    • Active + Hover: Active wins — hover rules intentionally target only neutral neighbors, avoiding conflicts.
    • Neutral + Neutral: No transparency rules apply; both keep borders.
  3. Comments explain the non-obvious "why" (border ownership model, precedence rules) rather than narrating code — appropriate for this kind of CSS complexity.

  4. The kind prop is confirmed to be passed directly to the DOM <button> element via BaseButton.tsx (line 91), so the attribute selectors (button[kind='segmented_control']) correctly match rendered elements.

  5. Minor observation: The existing &:focus-visible z-index rule (line 371-374) is now a subset of the new compound selector (line 346-350) for enabled buttons, but keeping it is harmless and adds clarity. The zIndex in StyledSegmentedControlButtonActive (line 385) is also pre-existing redundancy — not introduced by this PR.

  6. The CSS :has() pseudo-class is used for forward-looking sibling matching. Browser support is solid: Chrome 105+, Firefox 121+, Safari 15.4+, Edge 105+. This is consistent with the project's assumption that modern browser features are available (frontend/AGENTS.md notes "We assume the browser supports :focus-visible").

Test Coverage

E2E Tests (visual regression):

  • Added test_hovered_segmented_control_border_regression_snapshot using themed_app fixture, which covers both light and dark themes across chromium, firefox, and webkit (12 total snapshots).
  • Two well-chosen scenarios: (1) hover on a neutral-only group (multi-select, nothing selected) and (2) hover adjacent to a selected/active button (single-select with "North" selected, hovering "East").
  • The helper _assert_hover_border_snapshot includes a negative regression check verifying that hover does not change widget state.
  • Follows E2E best practices: key-based locators, screenshotting specific elements not the whole page, descriptive snapshot names (st_segmented_control-hover_border_*).

Frontend unit tests:

  • No new unit tests added. This is acceptable since the changes are purely CSS styling rules in Emotion styled components. Visual CSS behavior is best verified through the E2E visual regression snapshots rather than unit tests asserting CSS property values. The existing BaseButton.test.tsx doesn't test segmented control styles, and adding such tests wouldn't add meaningful coverage beyond what the visual regression snapshots provide.

Backwards Compatibility

No concerns. This is a CSS-only visual fix:

  • No API changes, no protobuf changes, no backend changes.
  • Only affects segmented control button visual rendering (not pills or other button types).
  • The updated existing snapshots (width, dynamic) reflect the improved border handling, confirming the fix applies consistently across all segmented control states.

Security & Risk

No security concerns. The changes are purely visual CSS adjustments with no changes to data flow, authentication, or server-side logic.

Regression risk is low:

  • The fix is scoped to segmented control buttons only (selectors target button[kind^='segmented_control']).
  • The border transparency rules are carefully gated by specificity (only between specific neighbor states).
  • The z-index value used (theme.zIndices.priority = 1) is the same as the pre-existing focus-visible z-index, so stacking context behavior is unchanged.

Accessibility

The changes maintain good accessibility practices:

  • :focus-visible is handled alongside :hover in the z-index elevation rule, ensuring keyboard-focused buttons are visually raised above neighbors.
  • The pre-existing &:focus-visible z-index rule is preserved as an additional safeguard.
  • No ARIA attributes are modified.
  • Focus rings and focus behavior are unaffected.

Recommendations

No blocking issues. Minor non-blocking observations:

  1. Consider testing edge buttons under hover: The current E2E test hovers "East" (second button). Testing hover on the first or last button could verify border-radius + border-transparency interaction, but the snapshot coverage across 3 browsers and 2 themes provides good confidence already.

  2. Pre-existing z-index redundancy: StyledSegmentedControlButtonActive (line 385) sets zIndex: theme.zIndices.priority which is now also covered by the new compound selector rule (line 346-350). A follow-up cleanup could remove the redundant declaration from the active styled component, but this is purely cosmetic.

Verdict

APPROVED: Well-crafted CSS bug fix with a clear border ownership model, comprehensive visual regression tests across themes and browsers, and no backwards compatibility or security concerns.


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

@sfc-gh-bnisco sfc-gh-bnisco force-pushed the 02-20-fix_button-group_keep_segmented_control_hover_borders_visually_consistent branch 4 times, most recently from e743ad6 to bf3e99c Compare February 23, 2026 18:57
@sfc-gh-bnisco sfc-gh-bnisco marked this pull request as ready for review February 23, 2026 19:27
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.

unrelated frustration: i hate when this happens 😢

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.

can't tell if this is a regression or not... looks like the options aren't vertically aligned anymore... (disclaimer: haven't looked at code yet)

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.

i don't see any code that would cause this, so i don't want to block on this. big 🤔 though

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.

but like with this one https://github.com/streamlit/streamlit/pull/14067/changes#r2843019361 it usually moves everything by a couple pixels... bothers me that the borders moved but not the text... maybe just subpixel math?

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.

Yeah I think this is a subpixel thing. I just compared the actual st_segmented_control.py running on this branch and in the nightly preview and they render exactly the same.

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.

whew, okay good to know

Copy link
Copy Markdown
Contributor

@sfc-gh-nbellante sfc-gh-nbellante left a comment

Choose a reason for hiding this comment

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

🙈

@sfc-gh-bnisco sfc-gh-bnisco force-pushed the 02-20-fix_button-group_keep_segmented_control_hover_borders_visually_consistent branch from bf3e99c to f34a1b7 Compare February 24, 2026 21:46
@sfc-gh-bnisco sfc-gh-bnisco force-pushed the 02-20-fix_button-group_keep_segmented_control_hover_borders_visually_consistent branch from f34a1b7 to 6fd50bb Compare February 24, 2026 21:58
@sfc-gh-bnisco sfc-gh-bnisco merged commit d3784e3 into develop Feb 24, 2026
44 checks passed
@sfc-gh-bnisco sfc-gh-bnisco deleted the 02-20-fix_button-group_keep_segmented_control_hover_borders_visually_consistent branch February 24, 2026 22:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

change:bugfix PR contains bug fix implementation feature:st.button Related to the `st.button` widget impact:users PR changes affect end users

Projects

None yet

Development

Successfully merging this pull request may close these issues.

On hover, the border for st.segmented_control changes color

3 participants