Skip to content

feat: restore visible keyboard focus via :focus-visible (#139) - #182

Merged
jenil merged 2 commits into
v1from
work/139-focus-visible
Aug 9, 2026
Merged

jenil merged 2 commits into
v1from
work/139-focus-visible

Conversation

@jenil

@jenil jenil commented Aug 9, 2026

Copy link
Copy Markdown
Owner

Card: #139 — restore visible keyboard focus via :focus-visible

Closes #139

Summary

Restores visible keyboard focus by replacing the outline: none + subtle 1px box-shadow :focus rule with a visible :focus-visible treatment on all interactive elements. Also resolves the a11y baseline findings (button-name, image-alt, link-name) tagged "fix in v1 -> #139" via fixture markup fixes.

This is the first production CSS change in v1 that touches shared form CSS. Per AGENTS.md, owner review is required for public CSS API changes.

Changes

Production CSS (src/_form.css)

  • Removed: outline: none + border-color + box-shadow on input:not(...):focus, select:focus, textarea:focus (the root cause of invisible focus)
  • Added: grouped :focus-visible rule on a, button, input, select, textarea, summary, [tabindex] with outline: 2px solid var(--color-primary); outline-offset: 2px;
  • Added: form-control :focus-visible rule preserving border-color + box-shadow (switched from :focus to :focus-visible so it no longer fires on mouse click — fixes a latent bug)
  • The two rules are split (grouped rule before :disabled, form-control rule after) to satisfy stylelint no-descending-specificity

a11y fixture fixes

  • test/index.html: .icon-only button gets aria-label="Search" + img alt="Search"
  • test/components.html: two .icon-only buttons get the same fix; .brand link with decorative img gets aria-label="Brand"

Tests

  • test/vrt/keyboard-focus.spec.js (NEW, 12 tests): 9 computed-style :focus-visible assertions on real Tab navigation + 3 focus-state VRT baselines
  • test/vrt/a11y-keyboard.spec.js (DELETED): was observation-only, replaced by keyboard-focus.spec.js
  • package.json: test:vrt includes keyboard-focus.spec.js; test:a11y removes the stale a11y-keyboard.spec.js reference
  • test/vrt/README.md: documents the new spec and coverage

Size baseline

  • baselines/size-baseline.json: 3326 → 3372 gzip bytes (+46, within 4096 ceiling) with rationale

Browser support

:focus-visible is supported across the entire browserslist:

  • Chrome 86+ (Oct 2020), Firefox 85+ (Jan 2021), Edge (Chromium-based)
  • Safari 15.4+ (the browserslist floor — :focus-visible was added in Safari 15.4)
  • iOS Safari 15.4+

No :focus fallback needed.

Behavior change

By design, :focus-visible shows the outline on keyboard focus only, not mouse click. This is the intended UX improvement. Form controls retain their border-color + box-shadow signal, but only on keyboard focus (previously fired on mouse click too — a latent bug now fixed).

Validation (local macOS)

  • yarn build: PASS, 3372 bytes (baseline 3372, ceiling 4096)
  • yarn test:vrt: 100 passed (was 88; +12 new keyboard-focus tests)
  • yarn test:a11y: 4 passed (button-name/image-alt/link-name gone; only deferred color-contrast/frame-title/scrollable-region-focusable remain)
  • yarn test:api: 5 passed (185 selectors / 14 custom properties unchanged)
  • git diff --check: clean

Linux baselines

The 3 focus-state VRT baselines are darwin-only (-chromium-darwin.png). Linux -chromium-linux.png baselines will be generated from CI failure artifacts (same pattern as #180/#181) and added before merge.

Scope

  • Allowed files: src/_form.css, dist/**, baselines/size-baseline.json, test/index.html, test/components.html, test/vrt/keyboard-focus.spec.js, test/vrt/a11y-keyboard.spec.js (deleted), test/vrt/snapshots/keyboard-focus.spec.js-snapshots/**, test/vrt/README.md, package.json
  • Protected: src/_base.css and other source partials, test/vrt/a11y-baseline.json (untouched — disappearing nodes auto-pass), playwright.config.js, .github/**, AGENTS.md, test/AGENTS.md, PLAN.md, other VRT specs
  • This is a production CSS change (first in v1 touching shared form CSS) — owner review required per AGENTS.md

jenil added 2 commits August 9, 2026 11:29
Replace the outline:none + subtle 1px box-shadow :focus rule with a
visible :focus-visible treatment on all interactive elements (a,
button, input, select, textarea, summary, [tabindex]). The new rule
applies outline: 2px solid var(--color-primary); outline-offset: 2px
on keyboard focus only — no mouse-click ring. Form controls retain
their border-color + box-shadow signal, switched to :focus-visible
so it no longer fires on mouse click (fixes a latent bug).

:focus-visible is supported across the entire browserslist (Safari
15.4+, iOS 15.4+, latest Chrome/Firefox/Edge), so no :focus fallback
is needed.

Resolve the a11y baseline findings tagged "fix in v1 -> #139":
  - .icon-only buttons get aria-label="Search" + img alt="Search"
    (button-name + image-alt)
  - .brand link with decorative img gets aria-label="Brand"
    (link-name)

Add test/vrt/keyboard-focus.spec.js (12 tests: 9 computed-style
:focus-visible assertions on Tab navigation + 3 focus-state VRT
baselines). Replace the observation-only a11y-keyboard.spec.js.
Wire keyboard-focus.spec.js into test:vrt; remove the stale
a11y-keyboard.spec.js reference from test:a11y.

Size: 3326 -> 3372 gzip bytes (+46, within 4096 ceiling). Baseline
bumped in baselines/size-baseline.json with rationale.

Local: yarn build PASS; yarn test:vrt 100 passed; yarn test:a11y
4 passed (button-name/image-alt/link-name gone); yarn test:api
5 passed.
Generated from CI failure artifacts on PR #182 (run 31321260633).
All 3 failures were 'snapshot doesn't exist, writing actual' — no
real visual diffs. The actuals are the Linux Chromium (ubuntu-latest)
focus-state renderings and are the required merge evidence per
test/vrt/README.md platform policy.
@jenil
jenil merged commit 78fbcf4 into v1 Aug 9, 2026
2 checks passed
@jenil
jenil deleted the work/139-focus-visible branch August 9, 2026 15:35
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.

1 participant