Skip to content

Fix revealjs screen reader fragment announcements - #15020

Merged
cderv merged 3 commits into
quarto-dev:mainfrom
jooyoungseo:fix/revealjs-screen-reader-announcements
Oct 13, 2026
Merged

cderv merged 3 commits into
quarto-dev:mainfrom
jooyoungseo:fix/revealjs-screen-reader-announcements

Conversation

@jooyoungseo

@jooyoungseo jooyoungseo commented Oct 9, 2026 •

Copy link
Copy Markdown
Collaborator

Fixes #15019.

Description

Backport the not-yet-merged hakimel/reveal.js#3946 to the bundled reveal.js 5.1.0 without upgrading to 6.x.

Hidden incremental content is excluded from .aria-status until it is revealed, including nested fragments and pauses. Fragment text is computed and announced only after all fragment classes are updated, so backward navigation uses the final visibility of nested normal and fade-out fragments. The fragment itself can still be announced when activated, and the built-in initially visible and transient styles retain their behavior.

URL handling no longer navigates back to the current slide or fragment after writing its own history entry. Named horizontal slide indices are normalized, explicit fragment links still work, and browser Back can return to the state before the first fragment when fragment-in-url is enabled.

The readable source patch is retained in package/src/common/patches/ and reapplied by the HTML dependency updater before rebuilding both JavaScript bundles and source maps. Before removing this backport, check that the upstream PR has landed in the release being bundled.

Validation

  • Eight Playwright scenarios cover actual live-region mutation counts, nested normal and fade-out fragments in both directions, pauses, fragment URLs, and browser Back/Forward.
  • 24/24 checks pass across Chromium, Firefox, and WebKit. The four new backward-navigation cases fail against the previous PR bundle on Chromium and pass after this revision.
  • Rebuilt the UMD and ESM bundles from 5.1.0. The source maps contain source changes only in js/reveal.js, js/controllers/location.js, and js/controllers/fragments.js. The readable patch applies cleanly to pristine 5.1.0 and reproduces these sources.
  • Local fixture rendering used Quarto 1.8.27, with the rebuilt bundles and current support plugin copied into the output before testing. The full Quarto build and test suite were not run.

Checklist

I have (if applicable):

  • referenced the GitHub issue this PR closes
  • updated the appropriate changelog in the PR
  • ensured the present test suite passes
  • added new tests
  • created a separate documentation PR in Quarto's website repo and linked it to this PR

No format option or documentation change is required.

@jooyoungseo
jooyoungseo requested review from cderv and a balanced review from Copilot October 9, 2026 20:28

Copilot AI left a comment

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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@posit-snyk-bot

posit-snyk-bot commented Oct 9, 2026 •

Copy link
Copy Markdown
Collaborator

✅ 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.

@cderv cderv left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks for this, and for the upstream PR too.

I found one case where the announcement is wrong when going backward. Fragments.update() announces the current fragment in the middle of its loop, before the classes of later fragments are updated (source):

if( i <= index ) {
  let wasVisible = el.classList.contains( 'visible' )
  el.classList.add( 'visible' );
  el.classList.remove( 'current-fragment' );

  if( i === index ) {
    // Announce the fragments one by one to the Screen Reader
    this.Reveal.announceStatus( this.Reveal.getStatusText( el ) );

So when stepping back onto a fragment that contains a nested later fragment, the nested one still has visible from the forward step when the parent text is computed. With the new class based check this gives:

---
format: revealjs
---

## A

<div class="fragment">Parent one <span class="fragment fade-out">Child fades</span></div>

## B

<div class="fragment">Parent two <span class="fragment">Child appears</span></div>

Stepping forward twice then back once on each slide, I get:

  • Slide A: "Parent one" is announced, but "Child fades" is shown again. The current bundle announces "Parent one Child fades" here.
  • Slide B: "Parent two Child appears" is announced, but "Child appears" is hidden again. Same as the current bundle.

The first one is a small regression, the second one is the same hidden content problem as #15019 but on the way back. I think announcing after the loop, once all fragment classes are updated, would fix both. It would mean touching fragments.js too. What do you think ? A backward step in the nested fragment test would cover it, and it would probably be worth having in hakimel/reveal.js#3946 as well.

Two small things:

  • The comment in update-html-dependencies.ts says it backports hakimel/reveal.js#3945, which is the issue. Could you point to hakimel/reveal.js#3946 and mention it is not merged yet ? This way whoever updates reveal.js next knows to check if the fix landed before removing the patch.

// Backport hakimel/reveal.js#3945 while Quarto bundles reveal.js 5.1.0.
// Remove the source patch and rebuild step when upgrading to a fixed release.

  • In the changelog, entries in a section are in ascending issue number, so the new one goes at the end of the Accessibility section, after #15015. Also add (author: @jooyoungseo) like for the #15011 entry.

@jooyoungseo

Copy link
Copy Markdown
Collaborator Author

Thanks, @cderv, you're right: the parent text was being computed before later fragments had their final classes. I've moved text extraction and announcements after the update loop, preserving the order of announcements for same-index fragments.

Both 5.1.0 bundles and source maps are rebuilt, and the source patch applies cleanly to pristine 5.1.0. As before, local fixtures were rendered with Quarto 1.8.27 and the rebuilt bundles/current support plugin; I have not run the full Quarto build or test suite.

@cderv cderv left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks for the quick follow-up, and for pushing the same change to hakimel/reveal.js#3946.

LGTM.

@cderv
cderv merged commit 2377f54 into quarto-dev:main Oct 13, 2026
51 checks passed
jooyoungseo added a commit to jooyoungseo/quarto-cli that referenced this pull request Oct 14, 2026
Apply the image announcement patch after the screen reader fragment patch from quarto-dev#15020, regenerate UMD/ESM bundles and source maps, and cover image names inside nested fragments.
cderv pushed a commit that referenced this pull request Oct 20, 2026
Each time a revealjs presentation became visible again, such as after
switching tabs, reveal.js moved focus to the body. Readers lost their place
on a link or chart, and screen readers such as NVDA and JAWS, which pass
arrow keys on only while focus is in the deck, dropped
back to browse mode. A new patch moves focus out of a frame and onto
the deck, and leaves focus where it is everywhere else.

- Add the revealjs-0003 patch, applied after 0001 and 0002
- Rebuild the reveal.js bundles and source maps from 5.1.0
- Add a Playwright spec for focus after the page is shown again

Fixes #15079

See #15020 and #15034 for the earlier revealjs screen reader fixes.
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.

revealjs: screen readers hear incremental content before it is revealed, and each slide twice

4 participants