Skip to content

[IRN-6372] [BpkBottomSheet] - aria title read on Android TalkBack#4198

Open
Stephen Hailey (steviehailey-skyscanner) wants to merge 5 commits intomainfrom
IRN-6372-opus
Open

[IRN-6372] [BpkBottomSheet] - aria title read on Android TalkBack#4198
Stephen Hailey (steviehailey-skyscanner) wants to merge 5 commits intomainfrom
IRN-6372-opus

Conversation

@steviehailey-skyscanner
Copy link
Contributor

@steviehailey-skyscanner Stephen Hailey (steviehailey-skyscanner) commented Feb 2, 2026

https://skyscanner.atlassian.net/browse/IRN-6372

fix(bottom-sheet): Add visually hidden title for screen reader nav announcement

When BpkBottomSheet has no visible title but has ariaLabel, the navigation bar's aria-labelledby was pointing to an empty element. This caused screen readers (particularly Android TalkBack) to fail to announce the navigation context when focusing on the close button.

This fix adds a visually hidden h2 element containing the ariaLabel text, which the navigation bar's aria-labelledby now references when no visible title is present.

Tested on physical Android device with TalkBack and now announces what the dialog is about based on aria-label. 🎉

Remember to include the following changes:

  • Ensure the PR title includes the name of the component you are changing so it's clear in the release notes for consumers of the changes in the version e.g [Clover-123][BpkButton] Updating the colour
  • README.md (If you have created a new component)
  • Component README.md
  • Tests
  • Accessibility tests
    • The following checks were performed:
      • Ability to navigate using a keyboard only
      • Zoom functionality (Deque University explanation):
        • The page SHOULD be functional AND readable when only the text is magnified to 200% of its initial size
        • Pages must reflow as zoom increases up to 400% so that content continues to be presented in only one column i.e. Content MUST NOT require scrolling in two directions (both vertically and horizontally)
      • Ability to navigate using a screen reader only
  • Storybook examples created/updated
  • For breaking changes or deprecating components/properties, migration guides added to the description of the PR. If the guide has large changes, consider creating a new Markdown page inside the component's docs folder and link it here

…nouncement

When BpkBottomSheet has no visible title but has ariaLabel, the navigation
bar's aria-labelledby was pointing to an empty element. This caused screen
readers (particularly Android TalkBack) to fail to announce the navigation
context when focusing on the close button.

This fix adds a visually hidden h2 element containing the ariaLabel text,
which the navigation bar's aria-labelledby now references when no visible
title is present.

Co-Authored-By: Claude Opus 4.5 <[email protected]>
@skyscanner-backpack-bot
Copy link

Visit https://backpack.github.io/storybook-prs/4198 to see this build running in a browser.

@skyscanner-backpack-bot
Copy link

skyscanner-backpack-bot bot commented Feb 2, 2026

Warnings
⚠️

Package source files (e.g. packages/package-name/src/Component.js) were updated, but snapshots weren't. Have you checked that the tests still pass?

Browser support

If this is a visual change, make sure you've tested it in multiple browsers.

Generated by 🚫 dangerJS against 4bd0582

Keep title as empty string rather than null to ensure nav's
aria-labelledby points to a different element than the hidden
title span, avoiding duplicate reading of ariaLabel.

Co-Authored-By: Claude Opus 4.5 <[email protected]>
Match PR element order - place visually hidden h2 after the
NavigationBar to prevent screen reader announcing it before
the nav context.

Co-Authored-By: Claude Opus 4.5 <[email protected]>
@skyscanner-backpack-bot
Copy link

Visit https://backpack.github.io/storybook-prs/4198 to see this build running in a browser.

).toBeInTheDocument();
});

it('renders a hidden h2 with ariaLabel text when no title is provided', () => {

Choose a reason for hiding this comment

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

Added by Opus - seem comprehensive to me and follow the patterns of surrounding tests ✅

}
/>
{/* Visually hidden title required for Android TalkBack to announce ariaLabel on BottomSheet open when no visible title provided */}
{showHiddenTitle && (

Choose a reason for hiding this comment

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

Same heading level as NavBar normally uses for visual Bottom Sheet title (when it has one)

Tested on Android with TalkBack via "No Header" story and now we do get the aria title announced on open while still maintaining focus on the close button. ✅

<header className={getClassName('bpk-bottom-sheet--header-wrapper')}>
<BpkNavigationBar
id={headingId}
id={showHiddenTitle ? hiddenTitleId : headingId}

Choose a reason for hiding this comment

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

Since we adding a Screen Reader heading below using headingId we don't want to pass the same id to Nav Bar and risk a duplicated element id inside with the same id so use an alternative in this case.


const headingId = `bpk-bottom-sheet-heading-${id}`;
const hiddenTitleId = `bpk-bottom-sheet-title-hidden-${id}`;
const showHiddenTitle = !title && 'ariaLabel' in ariaProps;

Choose a reason for hiding this comment

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

Detect if we're in the situation where we only have a heading for ScreenReader purposes to describe the dialog (no visual title)

Copy link
Contributor

Choose a reason for hiding this comment

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

would it be better to use const showHiddenTitle = !title && !!ariaProps.ariaLabel; to exclude the case that ariaLabel is ""?

Choose a reason for hiding this comment

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

Yeah, I think handling empty string is nice - especially when when our variable means we can change it in a single place and benefit from it each place we use it. The types insist that we still check for existence of the property before asserting it but that's fine.
e.g. will change to !title && 'ariaLabel' in ariaProps && ariaProps.ariaLabel

@steviehailey-skyscanner Stephen Hailey (steviehailey-skyscanner) marked this pull request as ready for review February 3, 2026 09:40
Copilot AI review requested due to automatic review settings February 3, 2026 09:40
Copy link
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 fixes an accessibility issue where Android TalkBack fails to announce the bottom sheet context when no visible title is present but an ariaLabel is provided. The solution adds a visually hidden heading element that screen readers can reference.

Changes:

  • Add visually hidden h2 element containing ariaLabel text when no visible title exists
  • Update navigation bar's aria-labelledby to reference the hidden title when appropriate
  • Add comprehensive test coverage for the new hidden title behavior

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
BpkBottomSheet.tsx Implements visually hidden title logic and imports BpkVisuallyHidden component
BpkBottomSheet-test.tsx Adds test cases for hidden title rendering and ariaLabelledby behavior

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +208 to +209
<BpkVisuallyHidden as="h2">
<span id={hiddenTitleId}>{ariaProps.ariaLabel}</span>
Copy link

Copilot AI Feb 3, 2026

Choose a reason for hiding this comment

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

The h2 element is redundant here. Since BpkVisuallyHidden is already rendering as an h2, wrapping a span with the id inside creates an unnecessary DOM nesting. The id should be placed directly on the h2 element instead. Change to: <BpkVisuallyHidden as=\"h2\" id={hiddenTitleId}>{ariaProps.ariaLabel}</BpkVisuallyHidden>

Suggested change
<BpkVisuallyHidden as="h2">
<span id={hiddenTitleId}>{ariaProps.ariaLabel}</span>
<BpkVisuallyHidden as="h2" id={hiddenTitleId}>
{ariaProps.ariaLabel}

Copilot uses AI. Check for mistakes.

Choose a reason for hiding this comment

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

Hey, I added the span because the visually hidden component has no id prop. I agree it could be handy for other scenarios based on it's use here.

  • are you suggesting I add one to it as part of this PR?

Choose a reason for hiding this comment

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

Ha just noticed this is from Copilot 😄

@skyscanner-backpack-bot
Copy link

Visit https://backpack.github.io/storybook-prs/4198 to see this build running in a browser.

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants