Skip to content

Fix keyboard navigation of tabsets in HTML documents without Bootstrap - #15007

Merged
cderv merged 3 commits into
mainfrom
fix/tabby-keyboard-730
Oct 8, 2026
Merged

cderv merged 3 commits into
mainfrom
fix/tabby-keyboard-730

Conversation

@cderv

@cderv cderv commented Oct 8, 2026

Copy link
Copy Markdown
Member

In HTML documents without Bootstrap (theme: none), tabsets use Quarto's copy of tabby. Pressing Tab on the selected tab moves focus to the next tab and selects it, so the content of the selected panel is never reachable from the keyboard. Arrow keys do nothing.

Root cause

tabby.js is upstream cferdinandi/tabby v12.0.3, which implements the WAI-ARIA tabs pattern (roving tabindex, arrow keys). A 2021 patch adapted it for revealjs, where arrow keys belong to slide navigation: every tab got tabindex="0", a tab became selected on focus, and key handling was limited to Home/End. Only the positive tabindex numbering was scoped to revealjs. The rest applied to every tabby user, including theme: none HTML.

Fix

Outside revealjs, tabby behaves as upstream again: only the selected tab is in the Tab order, arrow keys and Home/End switch tabs, and focus alone does not select a tab. This is the same behavior as Bootstrap tabs. revealjs keeps its current behavior; its focus order is discussed in #14970.

Handled keys now call preventDefault() and stopPropagation(). Without preventDefault(), Firefox and WebKit also scroll the page on Home/End. Without stopPropagation(), Home/End on a revealjs tab also reaches reveal's own keyboard handler, so it selects the tab and jumps to the first or last slide. That revealjs bug exists since the 2021 patch and is fixed here too.

revealjs detection keeps the existing body.reveal-viewport check rather than typeof Reveal !== "undefined". Reveal.initialize() sets this class on the element whose keyboard input reveal drives, so it tells whether reveal owns the arrow keys on the page, while the Reveal global only tells that the script was loaded. In Quarto's revealjs output both give the same answer, as tabsets are set up after Reveal.initialize().

Fixes #730

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
AI-assisted PR
  • AI tool used: Claude Code
  • Codebase grounding: local clone, upstream tabby source
  • Human review: I have reviewed, tested, and verified the AI-generated content before submitting.

Note: autonomous AI agents submitting PRs without human oversight are not permitted — see the Code of Conduct.

cderv added 2 commits October 7, 2026 17:08
…730)

HTML documents without Bootstrap (theme: none) use Quarto's copy of tabby
(upstream cferdinandi/tabby v12.0.3). A 2021 patch made revealjs tabsets
keyboard-usable by dropping the roving tabindex, activating tabs on focus,
and limiting key handling to Home/End, so arrow keys stay with slide
navigation. Only the positive tabindex numbering was scoped to revealjs; the
rest applied to every tabby user. In HTML, Tab from the selected tab then
lands on the next tab, selects it, and the panel content is unreachable.

Outside revealjs, tabby now behaves as upstream again: only the selected tab
is in the Tab order, arrow keys and Home/End switch tabs, and focus alone
does not select a tab. revealjs keeps its current behavior.

Handled keys now also call preventDefault: Firefox and WebKit otherwise
scroll the page on Home/End (and arrow keys) while switching tabs. Bootstrap
tabs do the same.
In revealjs, tabby selects the first or last tab on Home/End, but the
keydown event kept propagating to reveal's own keyboard handler, which
treats Home/End as "go to first/last slide". Selecting a tab from the
keyboard therefore also navigated away from the slide holding the tabset.

tabby listens on the tablist in the capture phase and reveal listens on
the document, so stopping propagation once tabby has handled the key
leaves reveal unaware of it. Outside revealjs, this also keeps the arrow
keys and Home/End that switch tabs from reaching any other page-level
keyboard handler, as with Bootstrap tabs.
@posit-snyk-bot

posit-snyk-bot commented Oct 8, 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 merged commit fb25b49 into main Oct 8, 2026
51 checks passed
@cderv
cderv deleted the fix/tabby-keyboard-730 branch October 8, 2026 13:42
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.

Keyboard tabbing order of tabset tabs and focusable controls within tabset content

2 participants