Repository navigation
Fix keyboard focus reaching off-screen slides in revealjs - #15010
Merged
Merged
Conversation
Collaborator
✅ Snyk checks have passed. No issues have been found so far.
💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse. |
…lide Reveal sets an inline `display: block` on slides within `viewDistance`, which defeats `hidden`, so Tab from the current slide reaches links and controls on nearby slides that are not shown (#14795). reveal.js 6.0.2 fixes this by toggling `inert` on every slide that is neither current nor an ancestor of the current slide. The bundled reveal.js 5.1.0 has no hook inside `updateSlidesVisibility()`, so the same condition is applied from `ready` and `slidechanged` until the bundled version is upgraded. The `slidechanged` listener is registered in the capture phase. This plugin is registered after format and user plugins, and a plain listener would run after theirs, while the new slide is still inert, so a plugin that focuses into the new slide would silently fail.
Non-present slides carry `inert`, which axe-core treats as hidden, so removing only `hidden` and `aria-hidden` would leave every off-screen slide unscanned. `revealUnhideSlides` now clears `inert` as well. On restore, `inert` is recomputed from live reveal state instead of replayed from a snapshot. A slide change while the scan is running would otherwise put back the stale value and leave the newly current slide inert.
Overview, scroll and print views show every slide, so `inert` must be cleared there. Without that, overview slides cannot be clicked to navigate. Leaving scroll view restores the slides' markup from a snapshot taken on entry, including whatever `inert` values they had then, and reveal.js 5.1.0 dispatches no event when that happens. The current slide could come back inert and unreachable by keyboard, so a `MutationObserver` on the viewport's `class` recomputes it. A `resize` listener does not work for the responsive scroll view: `resize` fires before the switch.
cderv
force-pushed
the
revealjs-off-screen-slides-stay-in-the-keyboard
branch
from
October 8, 2026 16:35
6b4f5df to
59064e7
Compare
This was referenced Oct 9, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
In
revealjspresentations, pressing Tab on the current slide can move focus to links and controls on nearby slides that are not shown. Reveal gives slides withinviewDistancean inlinedisplay: block, which defeats thehiddenattribute, andaria-hiddendoes not affect tab order.reveal.js fixed this upstream in 6.0.2 (hakimel/reveal.js#1587) by toggling
inerton every slide that is neither the current slide nor an ancestor of it, except in overview, scroll and print views where all slides are shown. Quarto bundles reveal.js 5.1.0, and moving to 6.x is a separate upgrade, so this adds the same rule to Quarto'ssupportplugin. The block is marked for removal once the bundled reveal.js is 6.0.2 or later.Fix
updateInertSlides()insupport.jscopies reveal's 6.0.2 condition verbatim. It is unconditional, with no format option, and it is skipped in print view. reveal.js 5.1.0 has no hook insideupdateSlidesVisibility(), so it runs from events:readyandslidechanged. Theslidechangedlistener is registered in the capture phase.supportregisters after format and user plugins, and a plain listener would run after theirs while the new slide is still inert, so a plugin that focuses into the new slide would silently fail.overviewshownandoverviewhidden. Inert slides do not receive pointer events, so overview needs it cleared to keep click-to-navigate working.MutationObserveron the viewportclass. Leaving scroll view restores the slides' markup from a snapshot taken on entry, with whateverinertvalues they had then, and reveal.js 5.1.0 dispatches no event when that happens. Scroll from slide 3 to slide 7, leave scroll view, and slide 7 would come back inert and unreachable by keyboard. Aresizelistener does not help for the responsive scroll view, becauseresizefires before the switch.The axe checker has to follow. It un-hides slides to scan them all, and the bundled axe-core treats
inertsubtrees as hidden, so violations on non-current slides would silently stop being reported.revealUnhideSlides()now liftsinertas well, and the restore recomputes it from the live reveal state instead of replaying saved values, so a slide change during the scan cannot leave the current slide inert.Fixes #14795
Checklist
I have (if applicable):
AI-assisted PR
Note: autonomous AI agents submitting PRs without human oversight are not permitted — see the Code of Conduct.