Restore keyboard focus indicator on bare .btn buttons - #14785
Conversation
The code tools button, website sidebar toggle, and website sidebar search button carry Bootstrap's btn class with no btn-* variant class. Bootstrap's .btn:focus-visible sets outline: 0 and substitutes a box-shadow that only the variant classes define, so these buttons took keyboard focus with no visible indicator, failing WCAG 2.4.7. Restore the browser's native focus ring on them with outline: revert. An outline also survives forced-colors mode, where box-shadow computes to none, and matches the code copy button's indicator (#12118).
✅ 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. |
There was a problem hiding this comment.
Thanks for this, and for the very thorough issue that goes with it. I checked the claims in the description against the compiled CSS and a real browser rather than taking them on trust, and the diagnosis holds up in full: with the rule commented out the three buttons compute outline-style: none, box-shadow: none, and an empty --bs-btn-focus-shadow-rgb. I also ran the new spec locally in all three engines: nine pass with the fix and nine fail without it, so the assertion is doing real work.
One substantive request, on the .quarto-secondary-nav prefix, plus two small things. Details inline.
The rest checks out.
| // Restore the browser's ring; unlike box-shadow it survives forced colors. | ||
| // See https://github.com/quarto-dev/quarto-cli/issues/14774 | ||
| .quarto-secondary-nav .quarto-btn-toggle:focus-visible, | ||
| .quarto-secondary-nav .quarto-search-button:focus-visible, |
There was a problem hiding this comment.
Could we drop the .quarto-secondary-nav prefix from these two selectors? It is the only reason the nav buttons end up with a different override story from the code tools button, and it does not appear to buy anything.
I tested this. Rendering a site with a user SCSS rule setting outline: 3px dashed red on all three selectors, the code tools button picks up the override (same specificity, user rules come later) while the two nav buttons keep the user-agent ring. With the prefix removed and nothing else changed, the override wins on all three. And without any user rule, the unprefixed selectors still beat Bootstrap: outline-style computes to auto on both nav buttons, because Quarto's rules layer already comes after the framework layer, so equal specificity plus later source order is enough.
Scoping is not lost either I think. Both buttons are emitted in exactly one place, nav-before-body.ejs, and only ever inside .quarto-secondary-nav.
That would leave all three buttons overridable the same way, and the description would no longer need the caveat about authors having to match the new specificity.
Did you check about this maybe ?
| @@ -0,0 +1,19 @@ | |||
| project: | |||
There was a problem hiding this comment.
This fixture needs a .gitignore, otherwise rendering it leaves an untracked _site/ behind (plus the .gitignore Quarto generates for .quarto/). The driver calls cleanoutput() without projectOutDir, so it cannot clean a project render — which is why the neighbouring website fixture ships one:
/.quarto/
/_site/
| target: Locator, | ||
| maxTabs = 25, | ||
| ): Promise<boolean> { | ||
| const tabKey = browserName === "webkit" ? "Alt+Tab" : "Tab"; |
There was a problem hiding this comment.
Not a change request, just a note for the record: on the Windows WebKit build the three tests also pass with a plain Tab, so the Safari tab-order behaviour the comment describes does not reproduce there. Alt+Tab passes as well and CI runs on Linux, so I would leave this as is — worth knowing if it ever gets flaky.
Description
Closes #14774.
Three Quarto buttons carry Bootstrap's
btnclass with nobtn-*variant class: the code tools button, a website's sidebar toggle, and a website's sidebar search button. Bootstrap's.btn:focus-visiblerule setsoutline: 0plus a box-shadow that only the variant classes define. On these buttons the box-shadow computes tonone, so keyboard focus shows no indicator. This fails WCAG 2.2 SC 2.4.7 (Focus Visible).This PR adds one rule that restores the browser's native focus ring on the three buttons with
outline: revert.Reasons to prefer the native ring over Bootstrap's box-shadow halo:
none.outline(Withforced-colors: active, focus-rings may be invisible twbs/bootstrap#42062), so the fix does not depend on a mechanism that upstream is retiring.revertsupport (Safari before 15.4) drop the declaration, and behavior is unchanged from today.Site authors can still restyle the focus state, because user SCSS compiles after Quarto's rules. For the code tools button the new selector has the same specificity as Bootstrap's, so any override that worked before still works (verified in a rendered document). For the two nav buttons an override must match the new selector's specificity, for example by including
.quarto-secondary-nav.No documentation change is needed: the fix adds no option and changes no documented behavior.
Tests
The new spec
html-focus-indicator-bare-btn.spec.tscovers all three buttons in Chromium, Firefox, and WebKit. It moves focus with real Tab presses (Option+Tab on WebKit, which ships Safari's default keyboard-navigation settings), then asserts that the computedoutline-styleis notnone. Real key presses are necessary because programmatic focus does not match:focus-visible. All nine tests fail on a build without the fix and pass with it. The fixtures are a standalonecode-toolsdocument and a minimal website with a collapsed sidebar.Checklist
I have (if applicable):
AI-assisted PR