Visible focus
outline:none is the most-copied accessibility bug on the web. Here's the fix.
If you only ever absorb one accessibility habit from this whole course, make it this one: never remove the focus ring without replacing it with something visible. The default browser ring is dorky, designers hate it, and the very first line of "reset CSS" most people copy is outline: none. That single line locks keyboard users out of a huge slice of the web every day.
The good news: keeping focus visible costs you almost nothing once you know the modern tools.
What focus is
Focus is the browser's pointer for the keyboard. At any moment, exactly one element on the page is "focused" — that's the element Enter, Space, or arrow keys will act on. Pressing Tab moves focus forward through interactive elements; Shift+Tab moves it back.
For a sighted keyboard user, the visible focus ring is the entire navigation system. Without it, they're typing into a void. Imagine using your phone with the cursor invisible.
outline:none and why it hurts
The default browser focus indicator — usually a blue or dotted outline — has been considered ugly since approximately 2003. So the most common CSS reset includes:
*:focus { outline: none; } /* please don't */That single rule wipes out the focus indicator on every element. A keyboard user pressing Tab now has no idea where they are. They might be on the "Delete account" button. They press Enter. Goodbye account.
WCAG 2.4.7 (Focus Visible) is unambiguous: when an element receives keyboard focus, the focus indicator must be visible. There's no escape hatch. If you remove the default, you must replace it.
outline: none with no replacement is the single most common keyboard accessibility
bug shipped on the web. Treat it like a syntax error.
:focus vs :focus-visible
Here's the historical tension. Designers don't want a focus ring to appear when a mouse user clicks a button — that looks like leftover state. But they do want it to appear when a keyboard user tabs to it. The problem: :focus triggers in both cases, because the element really is focused either way.
The CSS :focus-visible pseudo-class solves this. The browser uses heuristics to decide whether the focus is "interesting to show." Roughly: if you got there with Tab, yes; if you got there by clicking with a mouse, no. (Modern browsers handle the heuristic — you don't need to.)
/* Mouse-click focus stays clean, keyboard focus shows the ring */
button:focus { outline: none; }
button:focus-visible {
outline: 2px solid var(--accent);
outline-offset: 2px;
}That's the modern pattern in three lines. The first rule kills the ring for everyone, the second adds it back for the people who actually need it. (You can also skip the first rule and just style :focus-visible directly — many browsers will only paint the visible-focus indicator when it's needed.)
:focus-visible over :focus when designing a custom ring?Designing a focus ring you'll keep
You can design a focus ring that fits your brand and still passes:
- Make it at least 2 pixels thick. Anything thinner gets eaten by anti-aliasing on a busy background.
- Aim for 3:1 contrast against adjacent colors (1.4.11 again). The ring around a brand-colored button has to contrast with the button color and the page background.
- Use
outline-offsetto give the ring a little breathing room — it usually looks better and stays clear of the element's own border. - Don't replace the ring with a
box-shadowonly: in Windows High Contrast mode, box-shadows often disappear.outlinesurvives.
A tiny pattern you can paste into any project:
:where(button, a, input, textarea, select, [tabindex]):focus-visible {
outline: 2px solid var(--accent);
outline-offset: 2px;
border-radius: 4px;
}The :where() keeps specificity low so it's easy to override on specific components.
box-shadow: 0 0 0 3px tomato; only — no outline. A user runs Windows High Contrast Mode. What's most likely?A small checklist
When you ship a component, ask:
- Tab to it — can you see where focus is?
- Tab past it — does focus move forward without being trapped?
- Shift+Tab back — does it land where you expect?
- Click it with the mouse — is the ring suppressed (because you used
:focus-visible)? - Press Tab after the click — does the ring come back?
- Resize the window — does the ring stay visible at all sizes?
- Try Windows High Contrast (or
forced-colors: activein DevTools) — does the ring still appear?
That's it. Six checks, every component, every time. You're now permitted to delete the line outline: none from every file you touch and replace it with the better version.
Visible focus is solved. Next, we look at letting users tell you about themselves — the user preferences API.