ESC

Type to search the knowledge base.

Focus Visible Styles

Replace outline:none with :focus-visible designs that meet non-text contrast and stay out of the way of mouse users.

beginner3 min read
  • accessibility
  • focus-visible

Keyboard users need to see where focus is. outline: none without a replacement fails WCAG 2.4.7 Focus Visible. Modern CSS :focus-visible lets you show strong rings for keyboard focus while avoiding “stuck” rings on every mouse click for many controls.

Docs: MDN :focus-visible, WCAG 2.4.7, 2.4.13 Focus Appearance (2.2).

The anti-pattern

/* Don’t */
*:focus {
  outline: none;
}

Baseline replacement

:focus {
  outline: none; /* only if you replace below */
}

:focus-visible {
  outline: 2px solid var(--focus, #005fcc);
  outline-offset: 2px;
}

Or keep native outline for :focus-visible only:

:focus:not(:focus-visible) {
  outline: none;
}

Design requirements

Property Guidance
Contrast Focus indicator ≥ 3:1 against adjacent colors (non-text)
Thickness Clearly visible; WCAG 2.2 Focus Appearance adds metrics for AAA/AA targets — aim for obvious
Offset Helps on busy backgrounds
Shape Don’t rely on color alone — thickness/offset help
button:focus-visible {
  outline: 3px solid #005fcc;
  outline-offset: 3px;
  box-shadow: 0 0 0 1px #fff; /* separates from similar bg */
}

Components that need care

  • Dark buttons on dark backgrounds — ring must still contrast
  • Inputs with thick borders — offset rings
  • Cards that are links — focus on the whole hit target
  • Custom checkboxes — style the visible control, not only the native input if it’s visually replaced

Never remove focus for keyboard

Some designs show focus only on :focus-visible — good. Removing all focus indicators for “clean UI” is a product bug.

Testing

  1. Tab through the page — every interactive control shows a ring.
  2. Mouse click buttons — acceptable if ring hidden via :focus-visible behavior.
  3. High contrast / forced colors — check forced-colors media if you support Windows HCM.
@media (forced-colors: active) {
  :focus-visible {
    outline: 3px solid CanvasText;
  }
}

Footguns

  1. outline: none globally in a reset.
  2. Focus ring with <3:1 contrast.
  3. overflow: hidden clipping rings — use offset/shadow.
  4. Focusing elements that aren’t meant to be in tab order.

Interview out-loud answer

“I never remove focus styles without a visible replacement. I use :focus-visible with a high-contrast ring and offset, verify 3:1 non-text contrast, and keyboard-test every interactive control.”

Polyfills and older browsers

:focus-visible is widely supported now. For legacy support, the WICG polyfill or :focus fallback rings are acceptable. Never ship a global outline killer for “IE consistency” on modern stacks.

Extra practice

Write a minimal demo in a scratch file or the playground: one happy path, one failure path, and one boundary input. If you cannot exhibit a bug that the pattern prevents, you do not own the concept yet — re-read the primary docs linked below and tighten the example until the failure is obvious.

Notes from real codebases

Teams that succeed here keep the rules mechanical: lint where possible, CI for the rest, and a short human checklist for what automation cannot see. Document exceptions with an owner name and a removal date so “temporary” escapes do not become permanent architecture.

Further reading

Related guides