Skip to content

fix(button): forward expanded state to the interactive control - #5148

Open
pemalama12 wants to merge 1 commit into
callstack:mainfrom
pemalama12:fix/button-aria-expanded
Open

pemalama12 wants to merge 1 commit into
callstack:mainfrom
pemalama12:fix/button-aria-expanded

Conversation

@pemalama12

Copy link
Copy Markdown

Motivation

A Button used to show or hide content does not expose its expanded state on the interactive control.

For example, <Button aria-expanded={open} onPress={toggle}>Details</Button> puts aria-expanded on the outer Surface. The element with role="button" has no expanded state. Assistive technology cannot tell whether the content is open.

Forward aria-expanded to TouchableRipple alongside the existing accessibility props. The public prop type already supports this attribute.

Related issue

No existing issue found for this specific Button behavior. The reproduction and before/after results are included below.

Screenshots / Videos

No visual change.

Verified the actual DOM and accessibility tree in the running Expo web example:

Caller value Before: button attribute After: button attribute
true absent aria-expanded="true"
false absent aria-expanded="false"

After the fix, the outer Surface no longer carries the attribute.

Test plan

To repeat the manual web check:

  1. Render two Buttons inside PaperProvider. Give both an onPress handler. Pass aria-expanded={true} to one and aria-expanded={false} to the other.
  2. Inspect the interactive button elements in browser developer tools. Confirm their expanded states match the supplied values.
  3. Confirm the outer containers do not carry aria-expanded.

Verified these steps in the Codex in-app browser with the repository's Expo example app. I did not run iOS or Android device tests.

Automated validation with Node 24.13.0 and Yarn 4.9.1:

  • Both new accessibility-query regression cases failed before the fix and pass afterward.
  • yarn test --maxWorkers=2: 704 passed, 1 skipped; all 234 snapshots passed.
  • yarn typecheck: passed.
  • yarn lint: passed.
  • yarn prepack: passed.

Copilot AI balanced review requested due to automatic review settings October 27, 2026 07:01

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota.

An organization owner can choose the repository organization as the billing source for reviews requested by members with a paid Copilot license. Learn about Copilot code review billing.

@github-actions

Copy link
Copy Markdown

Found potential problems with the pull request:

  • The description is too long. Please keep it under 1000 characters.
  • Screenshot or video evidence is missing. Make sure to include one if it affects the UI.

@MikitasK MikitasK left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

good job 👍 👍
just a few comments from my side:

textColor: customTextColor,
children,
'aria-label': ariaLabel,
'aria-expanded': ariaExpanded,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

could you please declare 'aria-expanded' in Props with a short JSDoc (just like FAB.tsx does)

docs only list props declared in src/components, so right now it has no entry on Button docs page. aria-label & accessibilityHint above are declared for the same reason

onPressOut={hasPassedTouchHandler ? handlePressOut : undefined}
delayLongPress={delayLongPress}
aria-label={ariaLabel}
aria-expanded={ariaExpanded}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

verified natively on iPhone simulator:

Collapsed Expanded
Image Image

accessibility tree matches: button reports value: "expanded". ✅

[non-blocking] accessibilityState & aria-checked/aria-selected still land on the outer Surface so accessibilityState={{ expanded: false }} quietly contradicts this.
worth a follow-up issue?


expect(
screen.getByRole('button', { name: 'Show details', expanded })
).toBeEnabled();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[nit] expanded is asserted by query option (not by toBeEnabled()) so a regression shows up as "element not found" rather than real cause

RTL 14 has toBeExpanded() / toBeCollapsed(). splitting into two cases would assert it directly:

expect(screen.getByRole('button', { name: 'Show details' })).toBeExpanded()

@Doberjohn Doberjohn added this to the 6.0.0 milestone Oct 30, 2026

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants