From 74a6572475b1734459a7a692302c0c2e7fe7c97b Mon Sep 17 00:00:00 2001 From: le king fu Date: Thu, 8 Oct 2026 21:05:30 -0400 Subject: [PATCH] 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 --- src/styles.contrast.test.ts | 9 ++++++++- src/styles.css | 10 ++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/src/styles.contrast.test.ts b/src/styles.contrast.test.ts index b21369b..4c278b0 100644 --- a/src/styles.contrast.test.ts +++ b/src/styles.contrast.test.ts @@ -4,7 +4,8 @@ // - 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`); // - 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 { readFileSync } from "fs"; 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(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"); + }); }); diff --git a/src/styles.css b/src/styles.css index 48250c7..e49c13d 100644 --- a/src/styles.css +++ b/src/styles.css @@ -164,6 +164,16 @@ body { 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 { from { box-shadow: 0 0 0 0 color-mix(in srgb, var(--accent) 55%, transparent);