The outline: none Debate Has Been Over Since 2020
A designer flags the ugly blue focus ring in review. An engineer deletes it with outline: none. Nobody notices the keyboard-only user who now can't tell where they are on the page.

A design review flags it every time: a button gets clicked, and a chunky blue ring slaps itself around the edge for half a second. "Can we remove that? It looks broken." The engineer knows the one-line fix.
Ship it. The review passes. The button looks clean in every screenshot, in every browser, on every click.
Three weeks later an accessibility audit comes back with a line item: "Keyboard users cannot determine which element currently has focus." Nobody touched a mouse to find that bug. They tabbed through the page with their eyes closed to the cursor, because that's how they use the page every day — and your clean button is now indistinguishable from every other pixel on the screen.
That's not a nitpick. It's WCAG 2.4.7, a real success criterion, and outline: none on its own fails it every time.
The instinct isn't to put the ring back — nobody wants the ugly version either. It's to make it conditional: show it for keyboard users, hide it for mouse users, since a mouse click already gives you visual feedback that something happened. Reasonable goal. Before 2020, CSS had no way to ask "was this focus event a click or a keypress?" — so people answered the question in JavaScript instead:
This is roughly what the WICG's own focus-visible polyfill did under the hood, and plenty of design systems shipped a version of it. It worked. It also meant every page paid for a global event-listener setup, a class toggling on <body> on every focus change, and a heuristic that could still drift out of sync — tab into a field, click somewhere unrelated, tab again, and the class-based guess doesn't always match what actually happened.
Runs right in your browser — poke at it and watch the concept react live.
Browsers already knew whether a focus event came from a keyboard or a pointer — they use that exact signal internally to decide their own default outline behavior. :focus-visible just exposes it to your CSS.
:focus still matches on every focus event, same as always — a mouse click, a tap, a keyboard tab, a .focus() call from your own JS. :focus-visible only matches the subset of those where the browser's own heuristic decides a visible indicator is warranted. Tab to the button: it matches, the ring shows. Click the button with a mouse: it doesn't, no ring, and no JavaScript had to watch for either.
It shipped in Chrome and Edge in October 2020 (Chrome 86), and the rest followed within about two years — by 2022 it worked in every evergreen browser without a flag or a polyfill.
Try the pattern above on a <button> and a mouse click gives you no ring, exactly as expected. Try it on a text <input> and clicking it still shows the ring, even with your mouse.
That's not a bug in your CSS — the browser's heuristic treats form fields you're about to type into differently from buttons you just clicked. A button click is a complete action; you got your feedback from the click itself. A text field click is the start of typing, and you still need to see that the field is focused so you know where the caret is. :focus-visible accounts for that per element, not just per input device — which is exactly the nuance the old body.using-mouse class-toggle approach couldn't express, because it only ever knew about the page as a whole.
Think it clicked? Take the 6-question quiz →
Instant feedback, a hint on every question, and an explanation for each answer — right or wrong.
There's one case where deleting the ring outright is the right call, not a shortcut: an element that's tabindex="-1" and only ever receives programmatic focus your own script manages — a modal you focus on open, then visibly indicate some other way (a border, a heading, an announced live region). If a human never tabs to it and never clicks it, there's no keyboard user to leave stranded. Everywhere a real person can land with Tab, :focus-visible is the answer, not a suppressed outline and a hope that nobody notices.
Go find the *:focus { outline: none; } line in whatever you're shipping — or the using-mouse class hack if your codebase predates 2021 — and swap it for the two rules above. Which one does your codebase still have?
🚀 Want more like this? Every guide, playground, and quiz lives on bestpractic.org — open it and sign up free so the next one finds you.
Thanks for reading! Let's stay connected:
- ⭐ GitHub — follow me and star the projects: github.com/parsajiravand
- 💬 Discord — join the frontend best-practices community: discord.gg/d9KRhuAwQ
- 📸 Instagram — frontend best practices, daily: @bestpractice___
Keep reading
One post a day, in your inbox
Each one with a runnable playground and a quiz. No pitch, no digest, unsubscribe in one click.
0 comments
Sign in to join the discussion, like comments, and save articles for later.