fix(onboarding): give the focus ring back to the highlighted control

The ring of `[data-onboarding-highlight="true"]` is an author outline:
it wins over the browser's focus ring, so a keyboard user who reached
the highlighted control saw nothing change (WCAG 2.4.7, failure F78).
Measured by the review in headless Chromium, WebView2's engine: the
highlighted button kept `solid 3px` focused or not. `outline: revert`
under `:focus-visible` gives the browser's ring back, kept as is in the
built stylesheet; the contrast test reads the rule from the stylesheet.

Review of #398 (PR #404).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
le king fu 2026-10-08 21:05:30 -04:00
parent ec3b5d5185
commit 74a6572475
2 changed files with 18 additions and 1 deletions

View file

@ -4,7 +4,8 @@
// - WCAG 1.4.11: the ring reaches 3:1 against the page background and the // - WCAG 1.4.11: the ring reaches 3:1 against the page background and the
// cards, in the light theme (`:root`) and the dark one (`.dark`); // cards, in the light theme (`:root`) and the dark one (`.dark`);
// - WCAG 2.2.2: the pulse stops by itself after three cycles, within 5 s; // - WCAG 2.2.2: the pulse stops by itself after three cycles, within 5 s;
// - the pulse does not run under `prefers-reduced-motion: reduce`. // - the pulse does not run under `prefers-reduced-motion: reduce`;
// - WCAG 2.4.7: the browser's focus ring replaces the ring on keyboard focus.
import { describe, it, expect } from "vitest"; import { describe, it, expect } from "vitest";
import { readFileSync } from "fs"; import { readFileSync } from "fs";
import { resolve } from "path"; import { resolve } from "path";
@ -161,4 +162,10 @@ describe("styles.css — the getting-started highlight (#398)", () => {
expect(inner, "the highlight rule under reduced motion").toBeDefined(); expect(inner, "the highlight rule under reduced motion").toBeDefined();
expect(declarations(inner!.body).get("animation")).toBe("none"); expect(declarations(inner!.body).get("animation")).toBe("none");
}); });
it("gives the browser's focus ring back to the highlighted control (WCAG 2.4.7)", () => {
const rule = TOP.find((b) => b.selector === `${HIGHLIGHT}:focus-visible`);
expect(rule, "the focus-visible rule").toBeDefined();
expect(declarations(rule!.body).get("outline")).toBe("revert");
});
}); });

View file

@ -164,6 +164,16 @@ body {
animation-iteration-count: 3; animation-iteration-count: 3;
} }
/*
* An author outline wins over the browser's focus ring: without this rule, a
* keyboard user who reaches the highlighted control would see nothing change
* (WCAG 2.4.7). The browser's ring comes back while the control has the
* keyboard focus.
*/
[data-onboarding-highlight="true"]:focus-visible {
outline: revert;
}
@keyframes onboarding-pulse { @keyframes onboarding-pulse {
from { from {
box-shadow: 0 0 0 0 color-mix(in srgb, var(--accent) 55%, transparent); box-shadow: 0 0 0 0 color-mix(in srgb, var(--accent) 55%, transparent);