Why Focus Styles Matter

Keyboard navigation is a core interaction pattern—press Tab and you should move through every button, link, and form field in a logical order. For sighted users, that movement needs a clear visual cue. If your project shows no focus change, or one that’s barely perceptible, you’re creating a real barrier.

The Web Content Accessibility Guidelines (WCAG) address this under Success Criterion 2.4.7 (Focus Visible), which requires that users always know which element has keyboard focus. The upcoming WCAG 2.2 adds 2.4.11 (Focus Appearance Minimum), a draft criterion that clarifies how visible the focus indicator must be. You can start aligning with that stricter guidance now.

A Custom Property Foundation

Focus outlines often get tweaked per element or per context. Rather than hardcoding the same values in every rule, you can attach custom properties to your base interactive elements early in the cascade:

:is(a, button, input, textarea, summary) {
  --outline-size: max(2px, 0.08em);
  --outline-style: solid;
  --outline-color: currentColor;
}

:is(a, button, input, textarea, summary):focus {
  outline: var(--outline-size) var(--outline-style) var(--outline-color);
  outline-offset: var(--outline-offset, var(--outline-size));
}

This setup lets you override just the pieces you need. The --outline-size property uses max(), enforcing a minimum of 2px while still scaling relative to the element’s font size via 0.08em. That keeps outlines proportional on large buttons or headline links.

One property worth knowing is outline-offset. It controls the gap between the element and its outline, and can take negative values to pull the outline inside the element’s box—helpful when you need a contrast boost against a busy background. In the rule above, the offset falls back to the size value unless you supply your own --outline-offset.

Two Reasons Not to Remove Outlines

Outlines have historically been stripped out—by us and for us—because they looked “ugly.” But two modern changes make that harder to justify:

  1. outline now respects border-radius in Chromium and Firefox. You can retire the old box-shadow hacks that faked rounded focus rings. That also keeps focus styles intact for Windows High Contrast Theme users.
  2. :focus-visible lets the browser show focus only when needed. It applies heuristics based on input modality: keyboard users get the indicator, mouse users don’t when clicking. Note that form elements are exempt—they always receive a focus style.

You can combine this with your custom properties by layering :focus-visible into the rule set, while keeping the original :focus styles for older browsers:

:is(a, button, input, textarea, summary):focus-visible {
  outline: var(--outline-size) var(--outline-style) var(--outline-color);
  outline-offset: var(--outline-offset, var(--outline-size));
}

Keep these as separate rules—browsers discard selectors they don’t recognize, so merging them would break the fallback for :focus.

For browsers that do support :focus-visible, you’ll also want to suppress the default focus ring with a :focus:not(:focus-visible) rule:

:is(a, button, input, textarea, summary):focus:not(:focus-visible) {
  outline: none;
}

That extra rule matters because the latest Chromium and Firefox versions now apply :focus-visible behavior by default, and WebKit recently flipped the same switch, so it should land in Safari stable soon. Your custom outline rules remain necessary since you’re overriding the style—not just enabling the behavior.

The code block presented is a practical pattern for most projects; for deeper exploration of visible indicators, Sara Soueidan’s guide to focus indicators is a strong reference that accounts for the upcoming 2.4.11 criterion.