Skip to content

Make the collapsible callout header a keyboard-accessible button - #14981

Draft
cwickham wants to merge 3 commits into
mainfrom
fix/4934-callout-toggle-button
Draft

cwickham wants to merge 3 commits into
mainfrom
fix/4934-callout-toggle-button

Conversation

@cwickham

@cwickham cwickham commented Oct 2, 2026 •

Copy link
Copy Markdown
Member

Description

Fixes #4934. The header of a collapsible callout was a <div> with aria-expanded, no role, and no tab stop. Its aria-label="Toggle callout" also hid the title from screen readers.

The header now wraps the icon, title, and chevron in a native <button> (the APG disclosure pattern):

<div class="callout-header callout-collapsible d-flex align-content-center">
  <button type="button" class="callout-toggle-btn d-flex flex-fill collapsed"
          data-bs-toggle="collapse" data-bs-target=".callout-1-contents"
          aria-controls="callout-1" aria-expanded="false">
    <span class="callout-icon-container">…</span>
    <span class="callout-title-container flex-fill"><span class="screen-reader-only">Note </span>Title</span>
    <span class="callout-btn-toggle …"><i class="callout-toggle"></i></span>
  </button>
</div>
  • Keyboard users can reach the toggle with Tab and operate it with Enter or Space.
  • The name of the button is the callout type and the title, for example "Note Title".
  • The button target stays the .callout-N-contents class. Margin content that moves out of the callout keeps this class, so it still collapses with the callout.
  • The hidden type label now ends with a space. Without it, the button name was "NoteTitle".
  • Bootstrap updates collapsed on the trigger only. Thus the corner-radius rule now uses :has(> .callout-toggle-btn.collapsed) instead of a class on the header.

No visual change. I rendered 48 callouts with main and with this branch: all types, the default, simple, and minimal appearances, collapsed and expanded, static callouts, and margin content. All screenshots are pixel-identical in Chromium and WebKit, before and after a toggle. The only new visual is the focus ring on keyboard focus.

axe: the aria-allowed-attr finding (35 instances on the test page) is gone. A link in a collapsible title is now inside the button, so axe reports nested-interactive. Authors must not put links in a collapsible title.

Custom CSS: selectors for collapsible callouts change. .callout-header[aria-expanded] and .callout-header.collapsed become .callout-toggle-btn[aria-expanded] and .callout-toggle-btn.collapsed. The icon and title containers are now <span>, so div.callout-title-container no longer matches collapsible callouts. A GitHub code search found no hand-written CSS that uses the old header selectors. The one script it found adds role="button" to the header, which is a workaround for this issue. The changelog entry states the change.

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

Tests: a smoke-all test for the markup (callouts/a11y/issue-4934.qmd) and a Playwright keyboard test (html-callouts.spec.ts). The existing callout tests pass locally. I did not run the full suite locally; CI covers it.

AI-assisted PR
  • AI tool used: Claude Code
  • Codebase grounding: local clone
  • Human review: I have reviewed, tested, and verified the AI-generated content before submitting.

The collapse toggle was a div with aria-expanded, no role, no tab stop and
an aria-label that hid the title. The header now wraps its icon, title and
chevron in a <button> (APG disclosure pattern), so it is reachable with Tab,
operable with Enter/Space, and named by the callout type and title.

Rendering is pixel-identical to main in Chromium and WebKit for default,
simple and minimal callouts, collapsed and expanded, apart from the new
focus ring.

Fixes #4934
@posit-snyk-bot

posit-snyk-bot commented Oct 2, 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.

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.

Collapsed callouts using invalid ARIA attribute (a11y issue)

2 participants