Skip to content

Use the browser focus ring for sidebar section toggles - #14867

Draft
cwickham wants to merge 1 commit into
mainfrom
fix/converge-focus-indicator
Draft

Use the browser focus ring for sidebar section toggles#14867
cwickham wants to merge 1 commit into
mainfrom
fix/converge-focus-indicator

Conversation

@cwickham

@cwickham cwickham commented Sep 8, 2026

Copy link
Copy Markdown
Member

Description

Answers cderv's question on #14785:
two focus-indicator conventions were living next to each other in the website
sidebar. This PR settles on one — the browser's own focus ring — and deletes the
rule that departed from it.

#14826 gave the new sidebar section toggles their own ring:

.sidebar-item .sidebar-item-toggle:focus-visible,
.sidebar-item button.sidebar-item-text:focus-visible {
  outline: 2px solid currentColor;
  outline-offset: 2px;
}

That rule is not needed. The comment above it says a bare button gets no ring
from Bootstrap's reboot, but reboot only clears the ring for
:focus:not(:focus-visible) — the mouse-click case. These buttons carry no btn
class, so nothing suppressed their ring in the first place.

What the browser already does

I rendered a website with a navbar, a floating sidebar, search, code tools and a
color-scheme toggle, then tabbed through it with real key presses in Chromium,
Firefox and WebKit and read the computed style of every focused element. On
main today:

Control outline-style Comes from
a.navbar-brand auto browser
a.quarto-navigation-tool auto browser
button.quarto-color-scheme-toggle auto browser
button.aa-DetachedSearchButton auto browser
a.sidebar-item-text.sidebar-link auto browser
button.code-copy-button auto browser (#12118 removed a suppression)
button.btn.code-tools-button auto outline: revert (#14774)
button.quarto-btn-toggle.btn auto outline: revert (#14774)
button.sidebar-item-toggle solid 2px currentColor #14826

Firefox and WebKit agree, with their own ring widths (auto 1px and auto 3px).
So it is one rule against everything else, and the divergent one sits between two
sidebar links that use the browser ring. Tab down the sidebar and the indicator
changes shape mid-list.

With the rule removed, the toggles compute outline-style: auto in all three
engines and the ring is not clipped by the sidebar, so the outline-offset: 2px
was not doing work either.

Why the browser ring

  • Chromium and Firefox draw a two-tone ring, so it stays visible on any
    background. An authored ring cannot do that.
  • The color follows the platform accent color and the user's accessibility
    settings.
  • In forced-colors mode auto maps to the system highlight color. I measured it
    as present on every control that uses it.
  • Code block copy button has no visible indicator on keyboard focus #12118 already set the precedent. That fix
    (cb875f3fd,
    "copy-code: don't hide outline on :focus") deleted
    .code-copy-button:focus { outline: none; } and wrote no ring in its place.

The cost is that the ring differs between browsers. It already differs for every
link on the page, so this is not a new inconsistency.

Recording the convention

The convention is: never author a focus ring. The only focus rules Quarto
writes are ones that undo a vendor rule which suppressed the browser ring. There
is exactly one such rule, the outline: revert from #14785.

Two places say so now:

  • A comment on that rule in _bootstrap-rules.scss, including the note that a
    bare <button> with no .btn class needs no rule.
  • A "Focus indicators" section in .claude/rules/formats/sass-theming.md. Its
    paths: list gained src/resources/projects/**/*.scss, which it was missing —
    that glob is why the rule file did not surface when quarto-nav.scss was
    edited.

Not in this PR

The audit found four older focus-ring suppressions in quarto-search.scss,
including the line cscheid flagged on #12118 in April 2025,
where the search box in an overlay navbar has no focus indicator at all. They are
the same fault as #14774 but a separate change; I will file them on their own.

Separately, Bootstrap's own indicators for a.nav-link, button.navbar-toggler
and button.btn-close are box-shadow only, so they disappear in forced-colors
mode. Bootstrap 6 moves button focus styles to outline
(twbs/bootstrap#42062), so that
one is worth tracking rather than patching.

Tests

html-focus-indicator-bare-btn.spec.ts is renamed to
html-focus-indicator.spec.ts, so it is the home for focus-indicator tests
rather than for one issue, and it gains a test for the sidebar section toggles.
The fixture _quarto.yml gains a collapsible section, and index.qmd explains
both cases.

The assertion is not.toHaveCSS("outline-style", "none"), matching the three
tests already there. It checks the requirement — a visible indicator, WCAG 2.2
SC 2.4.7 — not which indicator, so it passes under either convention and does
not need rewriting if a theme restyles the ring. That does mean the new test
passes before and after this change; it is the safety net for the deletion, not
proof of it. To make sure it can fail, I added
.sidebar-item .sidebar-item-toggle:focus-visible { outline: none; }, re-rendered
and re-ran: 3 of 3 fail. With that probe reverted, 12 of 12 pass in Chromium,
Firefox and WebKit.

No changelog entry

#14826 and #14785 are both in the 1.11 cycle and both already have entries under
## Accessibility. This PR changes nothing that a user of 1.10 would see, so per
.claude/rules/changelog.md ("skip changelog entirely when the regression was
introduced by another change in the same version cycle") it needs no entry of its
own.

Checklist

I have (if applicable):

  • referenced the GitHub issue this PR closes — no issue; this answers the review question on Restore keyboard focus indicator on bare .btn buttons #14785
  • updated the appropriate changelog in the PR — not needed, see above
  • 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 — not needed, no option or documented behavior changes
AI-assisted PR
  • AI tool used: Claude Code
  • Codebase grounding: local clone of quarto-cli
  • Human review: I have reviewed, tested, and verified the AI-generated content before submitting.

The sidebar section toggles from #14826 drew their own focus ring
(`outline: 2px solid currentColor` with `outline-offset: 2px`). Every
other control in a Quarto page takes the browser's ring, so tabbing down
the sidebar stepped from a browser ring, to this one, and back again.

The comment on the rule said a bare button gets no ring from Bootstrap's
reboot. Reboot only clears the ring for `:focus:not(:focus-visible)`,
which is the mouse-click case, and these buttons carry no `btn` class,
so nothing suppressed their ring. Measured in Chromium, Firefox and
WebKit: with the rule gone the buttons compute `outline-style: auto`,
and the ring is not clipped by the sidebar.

Delete the rule, state the convention where the one `outline: revert`
rule lives, and record it in the sass-theming rules file. Extend the
focus-indicator spec to cover the section toggles.
@posit-snyk-bot

posit-snyk-bot commented Sep 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.

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.

2 participants