Skip to content

Improve version copy accessibility#14131

Merged
mayagbarnes merged 5 commits intodevelopfrom
menu-copy
Feb 27, 2026
Merged

Improve version copy accessibility#14131
mayagbarnes merged 5 commits intodevelopfrom
menu-copy

Conversation

@mayagbarnes
Copy link
Copy Markdown
Collaborator

@mayagbarnes mayagbarnes commented Feb 25, 2026

Describe your changes

Improves accessibility and layout of the version copy button in the MainMenu footer.

  • Move version footer outside role="menu": The StyledMenuVersionFooter (containing the CopyButton) is now a sibling of StyledMenuContainer rather than a child. This fixes a WAI-ARIA violation where an interactive button was nested inside role="menu" without being a menuitem. A new StyledMenuPopoverContent wrapper owns the outer padding and minWidth for both the menu and footer.
  • Keyboard navigation for the footer CopyButton: Tab from the last menu item moves focus to the CopyButton (via focus-lock), then a second Tab closes the popover. Escape from the CopyButton also closes the popover. Shift+Tab returns focus to the menu.
  • Remove hover jitter on copy button: Removed the transform: scale(0.9)scale(1) transition that caused visual jitter on hover. The button now fades in with a clean opacity transition only.

Testing Plan

  • JS Unit Tests: ✅ Added
  • E2E Tests: ✅ Updated
  • Manual Testing: ✅

@mayagbarnes mayagbarnes added security-assessment-completed change:refactor PR contains code refactoring without behavior change impact:users PR changes affect end users labels Feb 25, 2026
@snyk-io
Copy link
Copy Markdown
Contributor

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

✅ PR preview is ready!

Name Link
📦 Wheel file https://core-previews.s3-us-west-2.amazonaws.com/pr-14131/streamlit-1.54.0-py3-none-any.whl
📦 @streamlit/component-v2-lib Download from artifacts
🕹️ Preview app pr-14131.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 improves the accessibility and keyboard navigation of the MainMenu's version footer by restructuring the DOM to comply with WAI-ARIA standards and removing a visual jitter effect.

Changes:

  • Moved the version footer (containing the CopyButton) outside role="menu" to fix WAI-ARIA violation
  • Added keyboard navigation support for the CopyButton via Tab/Shift+Tab/Escape
  • Removed transform: scale() animation that caused hover jitter on the copy button

Reviewed changes

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

Show a summary per file
File Description
frontend/app/src/components/MainMenu/styled-components.ts Added StyledMenuPopoverContent wrapper, moved footer outside menu container, removed jittery scale transform
frontend/app/src/components/MainMenu/MainMenu.tsx Restructured DOM hierarchy, added handleFooterKeyDown for keyboard navigation, updated Tab handling logic
frontend/app/src/components/MainMenu/MainMenu.test.tsx Added comprehensive keyboard navigation tests for footer interactions
e2e_playwright/main_menu_test.py Updated selectors to target popover instead of menu for version footer
e2e_playwright/theming/custom_theme_fonts_test.py Updated selectors to target popover instead of menu for version footer

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

Consolidated Code Review

Summary

This PR improves the accessibility and layout of the MainMenu's version footer by:

  1. Moving the version footer outside role="menu": StyledMenuVersionFooter (with its CopyButton) is now a sibling of StyledMenuContainer rather than a child. A new StyledMenuPopoverContent wrapper owns outer padding and minWidth for both the menu and footer. This fixes a WAI-ARIA violation where an interactive <button> was nested inside role="menu" without being a menuitem.
  2. Adding keyboard navigation for the footer CopyButton: Tab from the last menu item moves focus to the CopyButton (via focus-lock), then a second Tab closes the popover. Escape from the CopyButton also closes the popover. Shift+Tab returns focus to the menu.
  3. Removing hover jitter on the copy button: The transform: scale(0.9)scale(1) transition is replaced with a clean opacity-only transition.

Files changed: 2 frontend source files, 1 frontend test file, 2 e2e test files (plus snapshot/CI updates).

Code Quality

Both reviewers agreed the code is clean, well-structured, and follows established patterns:

  • StyledMenuPopoverContent is well-documented with a JSDoc comment explaining the padding distribution between the wrapper and the footer.
  • handleFooterKeyDown follows the same pattern as handleKeyDown and correctly handles forward Tab (close), Shift+Tab (focus-lock cycles back), and Escape (close and return focus to trigger).
  • Spacing consistency is maintained: marginTop: theme.spacing.xs on StyledMenuVersionRow matches the flex gap that previously existed on StyledMenuContainer.

Key Disagreement: Shift+Tab Behavior

The main point of disagreement between reviewers was about Shift+Tab from menu items when the footer is present.

gpt-5.3-codex-high flagged this as a blocking regression: when streamlitVersion is set, the Tab case in handleKeyDown breaks out for both Tab and Shift+Tab, meaning Shift+Tab from a menu item cycles focus to the CopyButton via focus-lock rather than closing the menu. This was characterized as a "focus trap."

opus-4.6-thinking acknowledged the same behavior but considered it a deliberate, reasonable tradeoff, noting that users can always use Escape to dismiss.

Resolution: After examining the code, I agree with opus-4.6-thinking. The popover uses focusLock, which is designed to keep focus within the popover boundary — this is a standard accessible pattern for focus-locked containers. The focus cycle (menu items ↔ CopyButton) is the expected behavior of focus-lock, not a trap. Escape always provides an immediate exit path. While the strict WAI-ARIA menu-button pattern specifies that Tab/Shift+Tab should close the menu, the CopyButton intentionally lives outside role="menu" in a popover with broader interactive content — making the focus-lock pattern more appropriate than the bare menu-button pattern here. This is not a regression; it is a deliberate design choice.

Test Coverage

Both reviewers agreed that test coverage is good:

  • Frontend unit tests validate: CopyButton outside role="menu", forward Tab close from footer, Shift+Tab return from CopyButton, Escape from CopyButton, and menu staying open on Tab from menu items when footer exists.
  • E2E tests correctly updated to use popover locator instead of role="menu", and test_tab_closes_menu now tests the two-step Tab sequence.

Both reviewers identified the same gap: There is no unit test for Shift+Tab from a menu item when the footer is present (verifying the menu stays open and focus moves to the CopyButton). The existing Shift+Tab close test only covers the no-footer case. This would complete the coverage matrix but is non-blocking.

Backwards Compatibility

Both reviewers agreed there are no API-level breaking changes. The only behavioral change is that Tab from the menu now requires two presses to close (one to reach the CopyButton, one to close). Escape still closes the menu in one press. No protobuf or Python changes beyond E2E test updates.

Security & Risk

Both reviewers agreed: No security concerns. Regression risk is low — changes are well-scoped to the MainMenu component, and the conditional branching on streamlitVersion ensures the no-footer case continues to work exactly as before.

Accessibility

Both reviewers agreed this is a net positive for accessibility:

  • Fixes a real WAI-ARIA violation (interactive <button> was an invalid child of role="menu").
  • Keyboard navigation is well-designed: Tab → CopyButton → Tab to close; Escape to close from anywhere; Shift+Tab to go back.
  • Focus-lock integration is correct; returnFocus properly handles focus restoration for all close reasons.

Recommendations

  1. (Non-blocking) Add a unit test for Shift+Tab from a menu item when the footer is present, verifying the menu stays open and focus cycles to the CopyButton. Both reviewers identified this gap.

  2. (Non-blocking) Consider expanding the inline comment at line 707 in the handleKeyDown Tab case to explicitly note that the streamlitVersion check intentionally affects both Tab and Shift+Tab, as suggested by opus-4.6-thinking:

    case "Tab": {
      if (streamlitVersion) {
        // A CopyButton exists in the version footer outside role="menu"
        // but inside the popover's focus-lock.  Let focus-lock move
        // focus there (forward Tab) or cycle back (Shift+Tab) instead
        // of closing the menu immediately.
        break
      }

Verdict

APPROVED: A well-implemented accessibility improvement that fixes a real WAI-ARIA violation, adds clean keyboard navigation for the footer CopyButton, and includes comprehensive test coverage. The Shift+Tab cycling behavior within the focus-locked popover is intentional and consistent with standard focus-lock semantics — not a regression. The two non-blocking suggestions (additional test case and clarifying comment) would further strengthen the PR but are not required for merge.


Consolidated review by opus-4.6-thinking.


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

Summary

This PR improves the main menu's accessibility by moving the version footer (and its CopyButton) outside role="menu", updates keyboard handling for the footer, and aligns unit/e2e tests and snapshots with the new structure. The overall direction is good and addresses a real ARIA issue.

Code Quality

The refactor in frontend/app/src/components/MainMenu/MainMenu.tsx and frontend/app/src/components/MainMenu/styled-components.ts is clean and readable, and the semantic separation of menu items vs footer is a strong improvement.

One behavioral issue remains:

  • Reverse-tab regression / focus trap risk in frontend/app/src/components/MainMenu/MainMenu.tsx:706-716 and frontend/app/src/components/MainMenu/MainMenu.tsx:723-733:
    • When streamlitVersion is present, Tab handling in the menu always falls through (break), including Shift+Tab.
    • Shift+Tab on the footer intentionally returns focus to the menu, but Shift+Tab from the menu does not close the popover anymore, so reverse keyboard traversal can cycle inside the popover instead of moving to the previous page control.
    • This is a behavior change from prior menu behavior and from the no-footer path, which still closes on Shift+Tab.

Test Coverage

Coverage is generally good:

  • Frontend unit tests add checks for:
    • Footer being outside role="menu".
    • Forward Tab close behavior from footer.
    • Shift+Tab and Escape behavior from the footer.
  • E2E updates correctly retarget selectors from role="menu" to the popover container where needed.

Gap identified:

  • No test currently validates reverse escape from the popover when footer exists (e.g. Shift+Tab from menu should close and move backward, if that is intended), so the regression above is not caught.

Backwards Compatibility

No API-level breaking changes were found. However, keyboard behavior changed for users navigating backwards with Shift+Tab when the version footer is present, which is a backward-compatibility concern for keyboard interaction patterns.

Security & Risk

No direct security concerns identified.
Primary risk is interaction regression in keyboard navigation/focus management.

Accessibility

Positive:

  • Moving CopyButton outside role="menu" resolves an ARIA semantic violation and is a clear accessibility win.

Concern:

  • Current Shift+Tab behavior with the footer can trap reverse traversal inside the popover flow, which is an accessibility/usability regression risk for keyboard users.

Recommendations

  1. Update frontend/app/src/components/MainMenu/MainMenu.tsx keyboard logic so that with footer enabled:
    • Tab from menu continues to move to the footer copy button.
    • Shift+Tab from menu closes with "shift-tab" and returns focus backward (consistent with existing no-footer behavior).
  2. Add a targeted test in frontend/app/src/components/MainMenu/MainMenu.test.tsx for reverse-tab exit behavior when footer exists (and optionally an e2e assertion if desired).

Verdict

CHANGES REQUESTED: The accessibility semantics fix is strong, but reverse keyboard navigation has a regression risk that should be resolved before merge.


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 improves accessibility and layout of the version copy button in the MainMenu footer by:

  1. Moving the version footer outside role="menu": The StyledMenuVersionFooter (with its CopyButton) is now a sibling of StyledMenuContainer rather than a child. A new StyledMenuPopoverContent wrapper owns the outer padding and minWidth for both the menu and footer. This fixes a WAI-ARIA violation where an interactive <button> was nested inside role="menu" without being a menuitem.
  2. Adding keyboard navigation for the footer CopyButton: Tab from the last menu item moves focus to the CopyButton (via focus-lock), then a second Tab closes the popover. Escape from the CopyButton also closes the popover. Shift+Tab returns focus to the menu.
  3. Removing hover jitter on copy button: The transform: scale(0.9)scale(1) transition is replaced with a clean opacity-only transition.

Files changed: 2 frontend source files, 1 frontend test file, 2 e2e test files.

Code Quality

The code is well-structured, clean, and follows established patterns in the codebase:

  • StyledMenuPopoverContent (styled-components.ts:51-64): The new wrapper component is well-documented with a JSDoc comment explaining its purpose and how padding is distributed between it and the footer. The padding and minWidth properties were correctly migrated from the old StyledMenuContainer.

  • handleFooterKeyDown (MainMenu.tsx:723-743): The new keyboard handler is clean and follows the same pattern as handleKeyDown. It correctly handles forward Tab (close), Shift+Tab (let focus-lock cycle back), and Escape (close and return focus to trigger).

  • Tab behavior branching in handleKeyDown (MainMenu.tsx:707-716): The conditional if (streamlitVersion) check to decide whether to let focus-lock handle Tab or close immediately is a pragmatic solution. One thing to note: this condition also applies to Shift+Tab from menu items when the footer is present — Shift+Tab from the first menu item will cycle focus backward to the CopyButton via focus-lock instead of closing the menu. This is a deliberate tradeoff (users can always use Escape), but adding a brief inline comment noting that both Tab and Shift+Tab are affected would improve readability.

  • Spacing consistency: The marginTop: theme.spacing.xs added to StyledMenuVersionRow correctly matches the flex gap: theme.spacing.xs that previously existed on StyledMenuContainer, maintaining visual consistency. The footer's paddingLeft/Right: theme.spacing.sm aligns with menu item padding structure (StyledMenuItemRow also uses theme.spacing.sm horizontal padding).

Test Coverage

Frontend unit tests — Good coverage of the new keyboard navigation scenarios:

  • renders the CopyButton outside role=menu — directly validates the ARIA fix.
  • does not close the menu on Tab from a menu item when footer exists — validates the two-step Tab behavior.
  • closes the menu on forward Tab from the CopyButton — validates final Tab closes the popover.
  • does not close the menu on Shift+Tab from the CopyButton — validates backward Tab returns to menu.
  • closes the menu on Escape from the CopyButton — validates Escape closes from footer.

Tests correctly use positive + negative assertions (e.g., toBeInTheDocument vs not.toBeInTheDocument), consistent with the testing guidelines.

Minor gap: There is no unit test for Shift+Tab from a menu item when the footer is present (i.e., verifying the menu stays open and focus moves to the CopyButton). The existing Shift+Tab close test at line 304 only covers the no-footer case (getProps() without streamlitVersion). Adding one test case would complete the coverage matrix.

E2E tests — Updated appropriately:

  • test_tab_closes_menu now correctly tests the two-step Tab sequence (Tab to CopyButton, Tab to close).
  • test_main_menu_version_footer_visible and test_main_menu_version_footer_copies_version updated to use popover locator instead of menu locator, reflecting the new DOM structure.
  • Snapshot tests in main_menu_test.py and custom_theme_fonts_test.py updated to find version text via the popover wrapper instead of role="menu".

Backwards Compatibility

This change is backwards compatible for end users:

  • The visual appearance is preserved (padding, spacing, hover behavior all match).
  • The only behavioral change is that Tab from the menu now requires two presses to close (one to reach the CopyButton, one to close). This is a minor UX change but improves accessibility. Escape still closes the menu in one press.
  • No API changes, no protobuf changes, no Python changes beyond E2E test updates.

Security & Risk

No security concerns identified. The changes are purely in the frontend presentation layer and keyboard navigation logic. No new data flows, network calls, or state management changes.

Regression risk is low: The changes are well-scoped to the MainMenu component and its styled components. The conditional branching on streamlitVersion ensures the no-footer case continues to work exactly as before.

Accessibility

This PR is a net positive for accessibility:

  • Fixes a WAI-ARIA violation: The CopyButton is no longer an invalid child of role="menu". It now lives outside the menu container as a sibling in the popover, which is correct per the ARIA menu pattern (only menuitem, menuitemradio, menuitemcheckbox, and group/separator are valid children of role="menu").
  • Keyboard navigation is well-designed: Tab→CopyButton→Tab to close, Escape to close from anywhere, Shift+Tab to go back — all follow logical focus flow within the popover's focus-lock.
  • Focus-lock integration: The focusLock prop on StatefulPopover correctly keeps focus within the popover boundary, and the custom returnFocus callback properly handles focus restoration for all close reasons.
  • Minor note: Shift+Tab from menu items when the footer exists will cycle to the CopyButton rather than closing the menu. Per strict WAI-ARIA menu-button pattern, Shift+Tab should close the menu. However, since the CopyButton is an intentional interactive element in the popover, this tradeoff is reasonable — users can always dismiss with Escape.

Recommendations

  1. Consider adding a unit test for Shift+Tab from menu items when the footer is present. This would verify the menu stays open and focus moves to the CopyButton. Currently, Shift+Tab is only tested from the CopyButton (line 1617) and from the no-footer case (line 304).

  2. Consider adding a brief comment in the handleKeyDown Tab case (line 707) noting that the streamlitVersion check intentionally affects both Tab and Shift+Tab:

    case "Tab": {
      if (streamlitVersion) {
        // A CopyButton exists in the version footer outside role="menu"
        // but inside the popover's focus-lock.  Let focus-lock move
        // focus there (forward Tab) or cycle back (Shift+Tab) instead
        // of closing the menu immediately.
        break
      }

Both suggestions are minor improvements and are not blocking.

Verdict

APPROVED: A well-implemented accessibility improvement that fixes a real WAI-ARIA violation, adds clean keyboard navigation for the footer CopyButton, and includes comprehensive test coverage. The code is clean, follows existing patterns, and introduces no regressions.


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

@mayagbarnes mayagbarnes marked this pull request as ready for review February 26, 2026 09:16
@mayagbarnes mayagbarnes merged commit 1e785b0 into develop Feb 27, 2026
60 checks passed
@mayagbarnes mayagbarnes deleted the menu-copy branch February 27, 2026 07:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

change:refactor PR contains code refactoring without behavior change impact:users PR changes affect end users

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants