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

1 participant