Skip to content

fix(react-teaching-popover): move focus to page title on carousel step change - #36643

Draft
Paul Mardling (PaulGMardling) wants to merge 2 commits into
microsoft:masterfrom
PaulGMardling:fix/39651-teaching-popover-step-announcement
Draft

fix(react-teaching-popover): move focus to page title on carousel step change#36643
Paul Mardling (PaulGMardling) wants to merge 2 commits into
microsoft:masterfrom
PaulGMardling:fix/39651-teaching-popover-step-announcement

Conversation

@PaulGMardling

@PaulGMardling Paul Mardling (PaulGMardling) commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

Why

Screen readers (Narrator/NVDA) did not reliably announce the new step's title/content when navigating a TeachingPopoverCarousel via Next/Previous. The existing accessibility mechanism relies entirely on a live-region announce() call driven by a consumer-supplied announcement callback, which only receives the raw carousel item value (not the step index/total or title), so in practice announcements were often missing or unhelpful.

This is the "spec preferred" fix referenced in the underlying a11y bug report: move focus to the new page's title on step change, so assistive technology announces the heading directly — independent of (and in addition to) any live-region announcement text.

What changed

  • TeachingPopoverTitle now renders with tabIndex={-1} and a data-carousel-title marker by default (overridable via props).
  • Carousel's existing MutationObserver (already used to track carousel item add/remove) now also detects when a [data-carousel-title] node mounts and calls .focus({ preventScroll: true }) on it.
  • No new public props, context, or API surface — fully internal, backward-compatible.
  • Added regression tests covering focus moving to the title on Next/Previous navigation, and confirming focus is not moved on initial mount.
  • Added a beachball change file.

Testing

  • yarn nx run react-teaching-popover:test — all 312 tests pass (23 suites), including 3 new tests.
  • yarn nx run react-teaching-popover:lint — passes.
  • yarn nx run react-teaching-popover:type-check — passes (added @testing-library/jest-dom to tsconfig.spec.json types so toHaveFocus() type-checks, matching react-headless-components-preview).
  • yarn nx run react-teaching-popover:build — passes.
  • yarn nx run react-headless-components-preview:test — all 1032 tests pass (no regressions in the package that re-exports this one).
Screen.Recording.2026-08-28.at.14.11.58.mov

Fixes

ADO #39651
Related PR for flutentui-modern #5636212

…p change

Screen readers (Narrator/NVDA) did not reliably announce the new
step's title/content when navigating a TeachingPopoverCarousel via
Next/Previous, since the existing live-region announcement depends
entirely on the consumer-supplied `announcement` callback.

This adds the spec-preferred fix: every TeachingPopoverTitle now
renders with tabIndex=-1 and a data-carousel-title marker, and the
Carousel's existing MutationObserver moves focus to the new page's
title once it mounts. This lets assistive technology announce the
new heading directly, independent of the live-region text, without
introducing new public props or cross-component context plumbing.

Also adds @testing-library/jest-dom to the package's tsconfig.spec.json
types (and requires it in the jest setup) so toHaveFocus() works both
at runtime and under type-check, matching react-headless-components-preview.

Fixes ADO #39651.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions

github-actions Bot commented Aug 28, 2026

Copy link
Copy Markdown

📊 Bundle size report

Package & Exports Baseline (minified/GZIP) PR Change
react-components
react-components: all base hooks
217.385 kB
68.171 kB
217.753 kB
68.286 kB
368 B
115 B
react-components
react-components: entire library
1.283 MB
322.105 kB
1.283 MB
322.243 kB
368 B
138 B
react-headless-components-preview
react-headless-components-preview: entire library
238.112 kB
67.048 kB
238.483 kB
67.17 kB
371 B
122 B
react-headless-components-preview
@fluentui/react-headless-components-preview/teaching-popover
36.048 kB
11.996 kB
36.422 kB
12.125 kB
374 B
129 B
Unchanged fixtures
Package & Exports Size (minified/GZIP)
react-components
react-components: Button, FluentProvider & webLightTheme
67.461 kB
19.461 kB
react-components
react-components: Accordion, Button, FluentProvider, Image, Menu, Popover
227.126 kB
68.406 kB
react-components
react-components: FluentProvider & webLightTheme
40.684 kB
13.549 kB
react-headless-components-preview
@fluentui/react-headless-components-preview/tag-picker
53.988 kB
17.748 kB
react-portal-compat
PortalCompatProvider
5.341 kB
2.146 kB
react-teaching-popover
TeachingPopover
101.77 kB
32.204 kB
react-timepicker-compat
TimePicker
142.027 kB
46.435 kB
🤖 This report was generated against 25b0646fa18bb93e2d7ed052379b852a1f45d8f4

@github-actions

Copy link
Copy Markdown

Pull request demo site: URL

…igations

Previously the MutationObserver moved focus to *any* [data-carousel-title]
node added anywhere under the carousel, regardless of why it was added.
Since TeachingPopoverTitle always carries that marker, a consumer
rendering/async-loading a title anywhere in the carousel (e.g. content
loaded after the active page already mounted) could unexpectedly steal
focus, even though no Next/Previous navigation occurred.

Track which page value is expected to become active (set when the
carousel's value changes, cleared after use) and only move focus to a
title when it belongs to that page - resolved via the closest
[data-carousel-item] ancestor, since an item's root element persists in
the DOM across navigation (only its children are added/removed).

Adds a regression test covering a title mounting on the active page
outside of a navigation, and confirms existing focus-on-navigation
behavior still passes.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
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.

1 participant