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);