Skip to content

fix: add aria-labels to sidebar collapse and expand buttons#14563

Open
costajohnt wants to merge 3 commits intostreamlit:developfrom
costajohnt:fix/sidebar-aria-labels
Open

fix: add aria-labels to sidebar collapse and expand buttons#14563
costajohnt wants to merge 3 commits intostreamlit:developfrom
costajohnt:fix/sidebar-aria-labels

Conversation

@costajohnt
Copy link
Copy Markdown

Summary

  • Add aria-label="Collapse sidebar" to the sidebar collapse button in Sidebar.tsx
  • Add aria-label="Expand sidebar" to the sidebar expand button in Header.tsx
  • Add tests verifying both buttons have accessible labels

These icon-only buttons had no accessible text, making them invisible to screen readers.

Partial fix for #8399

Add accessible labels to the icon-only sidebar buttons so screen
readers can identify them. The collapse button gets "Collapse sidebar"
and the expand button gets "Expand sidebar".

Closes streamlit#8399
@snyk-io
Copy link
Copy Markdown
Contributor

snyk-io Bot commented Mar 29, 2026

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

Status Scan Engine 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

Thanks for contributing to Streamlit! 🎈

Please make sure you have read our Contributing Guide. You can find additional information about Streamlit development in the wiki.

The review process:

  1. Initial triage: A maintainer will apply labels, approve CI to run, and trigger AI-assisted reviews. Your PR may be flagged with status:needs-product-approval if the feature requires product team sign-off.

  2. Code review: A core maintainer will start reviewing your PR once:

    • It is marked as 'ready for review', not 'draft'
    • It has status:product-approved (or doesn't need it)
    • All CI checks pass
    • All AI review comments are addressed

We're receiving many contributions and have limited review bandwidth — please expect some delay. We appreciate your patience! 🙏

The previous querySelector("button") approach would return null
because the data-testid is on the button element itself via
BaseButton prop forwarding. Use getByRole with name option
instead, which is the idiomatic testing-library pattern and
directly validates the accessible interface.
Copy link
Copy Markdown
Contributor

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

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

Summary

This PR adds aria-label attributes to two icon-only sidebar toggle buttons that previously had no accessible name, making them invisible to screen readers:

  • aria-label="Collapse sidebar" on the sidebar collapse button in Sidebar.tsx
  • aria-label="Expand sidebar" on the sidebar expand button in Header.tsx

Both changes are accompanied by unit tests. This is a partial fix for #8399.

Reviewer consensus: All three reviewers (claude-4.6-opus-high-thinking, gemini-3.1-pro, gpt-5.3-codex-high) approved this PR unanimously. No critical or blocking issues were raised.

Code Quality

The implementation is minimal, targeted, and correct. Adding aria-label directly to the BaseButton components follows the frontend/AGENTS.md accessibility guideline that "icon-only buttons/links must have aria-label." The labels are clear and descriptive. No new abstractions, side effects, or behavior changes are introduced beyond the accessibility improvement.

Test Coverage

Both new tests verify the presence of the aria-label:

  • Header test: Uses getByRole("button", { name: "Expand sidebar" }), the preferred RTL query per the query priority order.
  • Sidebar test: Uses within(getByTestId(...)).getByRole("button") + toHaveAttribute, consistent with existing patterns in that file.

Two minor test improvements are suggested via inline comments (redundant toBeInTheDocument() assertion, and test consistency). Neither is blocking.

No E2E tests are added, which is appropriate — aria-label attributes are straightforward HTML attributes best validated by unit tests. The existing E2E sidebar tests cover functional sidebar behavior.

Backwards Compatibility

No breaking changes. Adding aria-label attributes to existing buttons is purely additive and does not affect any existing behavior or public API.

Security & Risk

No security concerns. The changes are limited to adding static aria-label string attributes to two buttons in the frontend UI. No new dependencies, network calls, dynamic content, or sensitive code paths are involved.

External test recommendation

  • Recommend external_test: No
  • Triggered categories: None
  • Evidence:
    • frontend/app/src/components/Header/Header.tsx: Adds a static aria-label attribute — no routing, auth, embedding, or cross-origin impact.
    • frontend/app/src/components/Sidebar/Sidebar.tsx: Adds a static aria-label attribute — no external behavior change.
  • Suggested external_test focus areas: N/A
  • Confidence: High
  • Assumptions and gaps: None — these are purely presentational accessibility attributes with no effect on network, session, or embedding behavior.

Accessibility

This PR is specifically an accessibility improvement. The changes directly address the frontend/AGENTS.md guideline: "Focusable controls must have an accessible name — icon-only buttons/links must have aria-label." Both sidebar toggle buttons now have descriptive labels that screen readers can announce, making the sidebar collapse/expand functionality accessible to users of assistive technology. All three reviewers confirmed the implementation is correct.

Recommendations

  1. In Header.test.tsx, replace .toBeInTheDocument() with .toBeVisible(). Per frontend/AGENTS.md testing guidelines, using getByRole (which throws if not found) with .toBeInTheDocument() is redundant. All three reviewers flagged this.
  2. Consider standardizing the Sidebar test to also use getByRole with the name option (matching the Header test pattern), which better reflects the RTL query priority order and directly validates the accessible interface.

Verdict

APPROVED: Clean, minimal accessibility fix that adds aria-labels to two icon-only sidebar buttons. All three reviewers approved unanimously. Only minor test assertion improvements were suggested — no blocking issues.


Consolidated AI review by claude-4.6-opus-high-thinking from 3/3 expected reviews (claude-4.6-opus-high-thinking, gemini-3.1-pro, gpt-5.3-codex-high). All reviews completed successfully.

This review also includes 2 inline comment(s) on specific code lines.


expect(
screen.getByRole("button", { name: "Expand sidebar" })
).toBeInTheDocument()
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.

suggestion: Per frontend/AGENTS.md testing guidelines, getByRole already throws if the element is not found, so asserting .toBeInTheDocument() is redundant. Replace with .toBeVisible() for a meaningful assertion:

expect(
  screen.getByRole("button", { name: "Expand sidebar" })
).toBeVisible()

const collapseButton = within(
screen.getByTestId("stSidebarCollapseButton")
).getByRole("button")
expect(collapseButton).toHaveAttribute("aria-label", "Collapse sidebar")
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.

suggestion: Consider using getByRole with the name option instead of querying by test ID and checking the attribute. This mirrors the Header test pattern, better reflects how assistive tech interacts with the element, and follows the RTL query priority order (role > testid):

expect(
  screen.getByRole("button", { name: "Collapse sidebar" })
).toBeVisible()

@sfc-gh-nbellante sfc-gh-nbellante added area:accessibility Related to accessibility (a11y) compliance area:frontend Related to frontend aspects feature:st.sidebar Related to `st.sidebar` container change:bugfix PR contains bug fix implementation impact:users PR changes affect end users labels Apr 3, 2026
@lukasmasuch lukasmasuch requested a review from Copilot April 9, 2026 11:10
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

Adds missing accessible names to the icon-only sidebar collapse/expand controls so screen readers can discover and announce them, along with unit tests to prevent regressions (partial fix for #8399).

Changes:

  • Add aria-label="Collapse sidebar" to the sidebar collapse button.
  • Add aria-label="Expand sidebar" to the header’s sidebar expand button.
  • Add unit tests asserting both controls have accessible labels.

Reviewed changes

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

File Description
frontend/app/src/components/Sidebar/Sidebar.tsx Adds aria-label to the sidebar collapse BaseButton.
frontend/app/src/components/Sidebar/Sidebar.test.tsx Adds a test intended to verify the collapse button label.
frontend/app/src/components/Header/Header.tsx Adds aria-label to the header expand-sidebar BaseButton.
frontend/app/src/components/Header/Header.test.tsx Adds a test verifying the expand button is queryable by accessible name.


const collapseButton = within(
screen.getByTestId("stSidebarCollapseButton")
).getByRole("button")
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

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

This test is likely to fail because the collapse button is rendered with visibility: hidden until the sidebar header is hovered (see StyledCollapseSidebarButton). getByRole("button") filters out hidden elements by default, so it may not find the button here. Consider hovering stSidebarHeader before querying, or query with { hidden: true } and assert the accessible name/label on the underlying button element.

Suggested change
).getByRole("button")
).getByRole("button", {
hidden: true,
name: "Collapse sidebar",
})

Copilot uses AI. Check for mistakes.

expect(
screen.getByRole("button", { name: "Expand sidebar" })
).toBeInTheDocument()
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

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

getByRole(...) already throws if the element isn't found, so toBeInTheDocument() is redundant here and goes against the repo's RTL testing guidance. Prefer asserting something meaningful like toBeVisible() (or drop the extra assertion and just rely on getByRole).

Suggested change
).toBeInTheDocument()
).toBeVisible()

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:accessibility Related to accessibility (a11y) compliance area:frontend Related to frontend aspects change:bugfix PR contains bug fix implementation feature:st.sidebar Related to `st.sidebar` container impact:users PR changes affect end users

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants