Scalable Focus Indicators Meet AA/AAA Criteria

Focus visibility is WCAG 2.2 AA (focus-visible) and AAA (focus-appearance, new in 2.2). AA requires keyboard interfaces to show focus indicators like browser outlines. AAA demands distinguishable indicators: at minimum, 2px thick outlines contrasting non-focused states.

Default browser outlines often fail AAA (too thin or low-contrast). Eckles fixes this with custom properties and max() for responsive sizing:

:root {
  --outline-w: max(2px, 0.15em);
  --outline-o: max(2px, 0.15em);
}

*:focus-visible {
  outline: var(--outline-w) solid currentColor;
  outline-offset: var(--outline-o);
}

max(2px, 0.15em) ensures ≥2px width (scales with font-size but never below 2px). currentColor inherits text color, passing contrast if text meets AA (4.5:1). For buttons where currentColor (white) fails against backgrounds, override to button's background color—outline-offset creates a gap for distinction.

This scales across components without per-element styles. Tradeoff: Test per-component contrast; currentColor assumes text contrast is solid.

Related AA/AAA criteria prevent author styles from hiding focus (AA: not entirely hidden; AAA: no part hidden). Browsers may obscure focus (e.g., Chrome clips outlines). Fix with scroll-margin-block: 10dvb on :focus—adds proportional viewport space (dynamic viewport units) without layout shifts. Only affects scroll position.

"The key take-away from the guidance for focus appearance is that any outline that is at least 2 pixels thick and contrasts with the non-focused appearance would pass this criterion." – Eckles on AAA minimums, enabling simple passes over complex tweaks (see Sara Soueidan for nuances).

Logical Focus Order Without DOM Rewrites

WCAG A (focus-order) requires tabbing to match logical/visual flow for screen readers/keyboard users. CSS like Grid/Flexbox order, position: sticky/absolute/fixed, anchor positioning, or masonry disrupts this—focus jumps randomly.

Best fix: Author DOM in visual order. If impossible, upcoming reading-flow/reading-order (Chrome-only; no Mozilla/WebKit support yet) linearizes focus. E.g., Grid rows: reading-flow: grid-rows tabs row-by-row without DOM/CSS changes.

Tradeoff: Unreliable cross-browser; prioritize source order. Eckles stresses: "When you are placing interactive elements in a layout, it's important to consider the actual order in the DOM as compared to what the final layout position."

Reflow at 400% Zoom Without Dual Scrolling

WCAG AA (reflow) demands single-column reflow at 400% desktop zoom (1280x1024px → 320x256px viewport) without content loss, functionality breaks, or horizontal scroll. This ~landscape iPhone 14 aspect ratio (narrower/wider than portrait mobile) trips mobile-first designs.

Common breaks: Sticky navs cover viewport; contained scrolls cutoff; fluid typography overflows; fixed px margins dominate (128px sections → 50%+ viewport at 400%).

Key insight: Viewport size ≠ device/user. Small viewports mean zoom, not mobile/touch—rearrange, don't remove features.

Fix spacing with min(128px, 25vh)—uses px normally, switches to proportional vh at zoom where px exceeds (as early as 200%). Viewport units scale computed values with zoom; px don't.

"Viewport size is not a proxy for device or user capabilities... a small viewport doesn't necessarily mean mobile. Doesn't necessarily mean touch. It may just mean a high zoom level is in use." – Eckles reframes responsive assumptions, preventing mobile-biased failures.

Theme Preferences via CSS Without Duplication

WCAG lacks dark mode mandates but stresses contrast (AA: 4.5:1 text) and non-color cues (A: use-of-color). Inclusive design honors prefers-color-scheme.

Set color-scheme: dark light; (prefers dark, supports light)—browser auto-applies system colors (Canvas background, CanvasText, etc.) to doc, links, forms, scrollbars for contrast. Overridable.

Use light-dark() (requires color-scheme): color: light-dark(#000, #fff);—single rule for themes, no media query duplication. Cards/buttons adapt smoothly.

Handle forced-colors: active (Windows High Contrast Mode, system-wide): Overrides colors (color, background-color, etc.) with system palette (Canvas, ButtonFace); removes shadows; adds backplates. Users customize palettes; can't opt-out/brand it.

Impacts: Lost shadows (replace with transparent borders → CanvasText); icon fills (use forced-colors-adjust: none for critical color); buttons lose emphasis (invert Canvas/CanvasText, but watch backplates).

Layer fixes in @layer forced-colors { ... } (last layer wins specificity). Transparent borders (border: 1px solid transparent) gain visibility sans query. Tripadvisor example: Semantic elements convert well; fixes for callouts, notifications, buttons.

"Users can customize forced-colored palettes... you cannot opt your site out... they might not even see your initial styles ever." – Eckles on forced-colors inescapability, prioritizing critical overrides (SVGs, workflows) over branding.

Key Takeaways

  • Define global :focus-visible with max(2px, 0.15em) outlines using currentColor for 2px+ AAA-compliant indicators.
  • Add scroll-margin-block: 10dvb to nudge focused elements into view without obscuring.
  • Author DOM in visual order; await reading-flow: grid-rows for CSS fixes.
  • Use min(128px, 25vh) for spacing to scale at 400% zoom reflow.
  • Declare color-scheme: dark light; + light-dark() for theme adaptation.
  • In @layer forced-colors, add transparent borders and forced-colors-adjust: none for critical elements.
  • Test 320x256px viewport, forced-colors emulator (Chromium rendering panel), and Windows themes.
  • Prioritize semantics; they auto-improve forced-colors handling (e.g., Tripadvisor).
  • Contrast text first—focus/forced-colors inherit wins.