outline: none Without a Replacement Focus Style
One line of CSS is quietly locking keyboard users out of your app. outline: none with no replacement removes the browser's focus ring — the only visual signal a keyboard user has for "you are here." On a login or checkout flow, that isn't a cosmetic bug; it's a locked door.
Why the focus ring exists
Every interactive element — links, buttons, inputs — gets a default focus outline from the browser. Sighted keyboard users (power users, people with motor impairments, anyone whose trackpad died) tab through the page and rely on that ring to know which element will receive their next keypress. Remove it and the page still works, but invisibly: focus moves, nothing on screen changes, and the user is navigating blind.
MDN's documentation for the outline property is blunt about it: if the default focus style is removed, the element must get an obvious replacement. WCAG codifies the same rule as success criterion 2.4.7 Focus Visible (Level AA): any keyboard-operable interface needs a mode where the keyboard focus indicator is visible.
This is not a niche problem. WebAIM's 2026 analysis of the top one million home pages found detectable WCAG failures on 95.9% of them — an average of 56 errors per page. Invisible focus is one of the classic contributors, and it usually enters the codebase as a single well-meaning "reset."
Why teams delete it in the first place
Designers see the ring flash when users click a button and call it ugly. That's because :focus matches on every focus event — mouse, touch, and keyboard alike. The old fix was outline: none, which "solved" the mouse case by breaking the keyboard case.
The modern fix: :focus-visible
:focus-visible matches only when the browser decides the user actually needs to see focus — in practice: yes for keyboard navigation, no for mouse clicks on most controls. It has been Baseline widely available across browsers since March 2022. That means you can have both outcomes at once:
/* 1. Quiet the ring for pointer clicks */
:focus:not(:focus-visible) {
outline: none;
}
/* 2. Give keyboard users a real indicator */
:focus-visible {
outline: 2px solid currentColor;
outline-offset: 2px;
}
currentColor keeps the ring on-brand automatically (it inherits the element's text color), outline-offset adds breathing room so the ring doesn't hug the border, and outlines cost nothing in layout — they take up no space and even follow border-radius in modern browsers.
Rules of thumb
| Situation | Do this |
|---|---|
| Ring flashes on mouse click | Style :focus-visible, not :focus |
Legacy outline: none in the codebase | Grep for it; pair every one with a :focus-visible rule |
| Custom focus style | ≥2px, high contrast vs. background, outline-offset: 2px |
| Text inputs | Keep a visible indicator on :focus too — browsers show it for pointer focus there by design |
| Very old browsers | @supports not selector(:focus-visible) fallback that styles :focus |
Power-user moves
- Audit in one command.
grep -rn "outline: *none\|outline: *0" src/styles/finds every offender. Each hit either gets deleted or gets a:focus-visiblepartner. - Make it a lint rule. Stylelint's a11y-oriented plugins can flag
outline: nonewithout an accompanying focus style, so the regression never ships again. - Test the way it fails. Put the mouse down and Tab through your critical flow — sign-in, add-to-cart, checkout. If you ever lose track of where you are, so does every keyboard user.
- Don't stop at the outline. A focus style that's visible but 1px and low-contrast still fails users in practice; MDN notes that changing focus styles carelessly can hurt usability almost as much as removing them.
Resources
:focus-visible— MDNoutline— MDN (accessibility warning onoutline: none)outline-offset— MDN- Understanding WCAG 2.4.7 Focus Visible — W3C
- The WebAIM Million — annual accessibility analysis
Building an AI feature? Yeda AI designs, audits, and ships production LLM systems.