Did your CSS delete the focus ring?
Enter any page. We follow the stylesheets it serves and read every rule that touches a focus indicator — the blanket reset, the removal with nothing in its place, the outline drawn in transparent — and tell you which of your controls a keyboard user can no longer see themselves on.
Free, instant, no signup and no card. Nothing is stored.
Free, instant, no signup and no card. We read the page’s HTML and follow the stylesheets it serves from its own domain. Nothing is stored.
One line, every control, no warning
Almost every other WCAG failure is retail. A missing alt attribute costs you one image. An unlabelled field costs you one field. This one is wholesale: *:focus { outline: none } sits in a reset stylesheet, loads on every page, and takes the indicator off every link, button, input, select and custom widget you will ever ship, in a single declaration that nothing in your toolchain objects to.
And it is invisible to the people who would catch it. A mouse never focuses anything, so nobody clicking around the site sees a difference. A screen reader announces the control regardless, so an assistive-tech smoke test passes. Visual regression suites screenshot pages at rest, never mid-Tab. Automated scanners are mostly silent, because from the outside “no ring” and “a ring drawn as a box-shadow” look the same until somebody reads the cascade.
The person it strands is sighted, not using a screen reader, and navigating with the keyboard because a mouse hurts or is not available — RSI, a tremor, a broken wrist, a dead trackpad, or preference. Without an indicator they are still moving through your site, they simply cannot see where. Pressing Enter becomes a guess. On a page with a destructive button in it, that is the whole argument for the criterion in one sentence.
Four rules that all contain “outline”, and only two are defects
This is why a grep is not a checker, and why scripts/check-focus-audit.mjs pins the false positives in the build as hard as the failures.
/* BROKEN — the ring is gone and nothing takes its place */
*:focus { outline: none; }/* CORRECT — replaced, not removed. WCAG names no mechanism. */
.btn:focus { outline: none; box-shadow: 0 0 0 3px rgba(11,95,255,.6); }/* CORRECT — the intended modern pattern, keyboard only */
:focus { outline: none; }
:focus-visible { outline: 2px solid #0b5fff; outline-offset: 2px; }/* BROKEN — an outline nobody can see, and it reads as a fix in a diff */
.btn:focus { outline: 2px solid transparent; }So the verdict on a removal is never a property of the rule itself — it is a property of what else the stylesheet says about the same control. The checker collects every removal and every restoration first, matches them up by what they are rules about, and only then decides. A restoration written on a different component does not excuse a removal on this one, and a rule inside @media print is dropped before anything is read, because hiding a ring from the printer is correct.
What this checker reports, and how hard
Every focus ring on the site is switched off
FailsWCAG 2.1 SC 2.4.7 Focus Visible (Level AA)
A blanket rule — the `*`, `:focus` or `a, button, input` shape — removes the outline from everything that can take focus, and nothing in this stylesheet puts an indicator back. That is the whole criterion gone in one line, on every page and every control: a keyboard user can still move through the site, but has no way of knowing what they are on before they press Enter.
The focus ring is removed with nothing in its place
FailsWCAG 2.1 SC 2.4.7 Focus Visible (Level AA)
This rule takes the outline off a control on focus and supplies no other visible change — no box-shadow, no border, no background, nothing. The control is reachable by keyboard and gives no sign when it is reached. It is worth knowing that the criterion does not ask for an outline specifically; it asks for something you can see, and this rule leaves nothing.
The focus outline is drawn in transparent
FailsWCAG 2.1 SC 2.4.7 Focus Visible (Level AA)
The outline is set to transparent — `outline-color: transparent`, or `outline: 2px solid transparent` — with no other indicator on the rule. There is an outline in the box model and there is nothing on the screen. This shape is almost always the result of someone being told to stop writing `outline: none`, and it reads as a fix in a code review while changing nothing a person can see.
The outline is switched off in every state, not just on focus
FailsWCAG 2.1 SC 2.4.7 Focus Visible (Level AA)
This rule has no `:focus` in its selector at all — it sets `outline: none` on the element outright. That is stronger than the usual defect: there is no state left in which the ring can appear, so no later `:focus` rule that only sets a colour or an offset can bring it back.
An element carries outline:none in its style attribute
FailsWCAG 2.1 SC 2.4.7 Focus Visible (Level AA)
The removal is written into the element's own `style=""` attribute rather than a stylesheet. This matters more than the same declaration in CSS, because an inline style beats every rule in every stylesheet short of `!important` — so the design system's focus styles cannot reach this control, and a site-wide fix will silently miss it.
A utility class removes the indicator — whether it lands is not a CSS question
VerifyThis rule belongs to a utility-first framework — Tailwind and its relatives, recognisable by the escaped colon in a class name like `.focus\:outline-none`. On those stacks the indicator is composed on the ELEMENT, not in the stylesheet: `focus:outline-none focus:ring-2` removes the ring and adds one back, in two separate classes sitting side by side in the markup. So the stylesheet alone cannot say whether any given control ends up with an indicator, and a checker that failed the site for owning the utility would be wrong about most of the modern web. It is reported so you know the shape is present, and to point at the one thing worth grepping.
The focus indicator is a hairline
ReviewThe indicator exists, so SC 2.4.7 is met — it asks for visibility, not for a size. This is flagged because a 1px ring against a mid-tone background is the indicator people report as missing even though it is there, and because WCAG 2.2 does put numbers on it: SC 2.4.13 Focus Appearance (AAA) wants an area at least as large as a 2px-thick perimeter, at 3:1 contrast against what it replaces.
The ring is off for :focus and on for :focus-visible
ReviewThis is the intended modern pattern, not a defect: the outline is suppressed for `:focus` so it does not appear on a mouse click, and `:focus-visible` puts it back for the keyboard. Current browsers all support it. It is listed only so you know it is deliberate, and because the fallback matters — in a browser without `:focus-visible` support the first rule applies and the second never does, which is a keyboard user with no indicator at all.
No focus styling at all — the browser default is doing the job
ReviewEvery stylesheet we read is silent on focus, which means the indicator on this page is whatever the browser draws by itself. That conforms today: a user agent default is a legitimate visible indicator, and Chrome, Firefox and Safari all ship one. It is worth knowing rather than fixing — the default ring is drawn without any knowledge of your palette, so on a dark control it can be close to invisible, and the first CSS reset anybody adds will remove it.
We could not read any of this page's CSS
VerifyThe page carries no `<style>` block we can read, and none of its stylesheets could be fetched — typically because they are served from a CDN on another origin, or behind a header we do not send. On this one criterion that means no verdict is possible in either direction, because the answer lives entirely in CSS.
Some stylesheets were not read
VerifyAt least one stylesheet on this page sits on another origin or was past the number we fetch, so it was not read. Anything in it — a removal or a restoration — is not in the result below.
Note what is not on that list as a failure. A page with no focus CSS at all is review, because the browser default is a conforming indicator and this is one of the few accessibility questions where doing nothing passes. A stylesheet we could not fetch is verify, listed by name, rather than quietly counted as clean — a checker that turned a gap in its own reading into a green tick would be worse than no checker.
The fix, in full
Replace the ring rather than deleting it. Three declarations, and the design objection that started the whole thing goes away too, because the indicator is now yours.
/* 1. Never suppress unconditionally. Delete any rule like this: */
/* *:focus, button:focus, a:focus { outline: none } */
/* 2. Define the indicator you want, for the keyboard. */
:focus-visible {
outline: 2px solid #0b5fff; /* 3:1 against the control AND the page */
outline-offset: 2px; /* clear of the control's own border */
border-radius: inherit;
}
/* 3. If the ring must not show on mouse clicks, scope the suppression
so a browser without :focus-visible keeps its default ring. */
:focus:not(:focus-visible) {
outline: none;
}Then check the places the cascade does not reach. An outline: none written into an element’s own style attribute beats every stylesheet you have, so a site-wide fix will silently miss it — this checker reports those separately for exactly that reason. Custom widgets built from <div>s with tabindex need the rule applied to them by hand, and anything drawn inside a canvas or a shadow root is a manual check.
Last, the part no source reader can judge: contrast. SC 1.4.11 Non-text Contrast wants the indicator at 3:1 against what is next to it, which a dark ring on a dark button fails while passing every check on this page. The colour contrast checker takes two colours and answers that one.
Common questions
- What is a focus indicator?
- The ring, halo or highlight a browser draws around whatever the keyboard is currently on. Press Tab on a page you did not build and watch it move from link to link — that moving marker is the indicator. It is the only thing telling someone who navigates by keyboard where they are, and WCAG 2.1 SC 2.4.7 Focus Visible asks for it at level AA.
- Why does outline: none break a website so quietly?
- Because the person who wrote it is using a mouse, and a mouse never focuses anything. The declaration usually arrives for a real reason — a designer objects to the blue ring appearing when a button is clicked — and it is typed once into a reset, applied to every control on every page, and produces no visible change to anyone testing with a pointer. Nothing errors, no build fails, and Lighthouse does not flag it, because a machine looking at the page cannot tell an absent ring from a ring drawn some other way.
- Is it ever fine to write outline: none?
- Yes, and this is the judgement the checker exists to make. `outline: none` followed by `box-shadow: 0 0 0 3px #0b5fff` in the same rule is correct — the indicator was replaced, not removed, and WCAG names no particular mechanism. A shadow, a border, a background swap, an underline all conform. This checker calls that a pass and never reports it. What it reports is a removal with nothing behind it.
- What about :focus-visible?
- That is the modern, intended pattern: suppress the ring on `:focus` so it does not appear on mouse clicks, and restore it on `:focus-visible` so it does appear for the keyboard. The checker recognises the pair and reports it for information rather than as a defect. The one caveat it gives you is the fallback — a browser that does not understand `:focus-visible` applies your suppression and never applies your restoration, so writing the suppression as `:focus:not(:focus-visible)` is strictly safer, because an unknown pseudo-class invalidates the whole selector and the browser keeps its default.
- Why is a transparent outline a failure?
- Because it is an outline nobody can see, and the criterion asks for a visible indication. `outline: 2px solid transparent` appears in real codebases for two reasons: as a Windows High Contrast Mode trick, where the forced-colors palette repaints it — legitimate, when a visible indicator is also supplied — and, far more often, as what somebody writes after being told to stop writing `outline: none`. It satisfies a code reviewer grepping for the wrong string and changes nothing on screen.
- My page has no :focus rules at all. Is that bad?
- No. Every browser ships a default focus indicator, and a user agent default is a conforming visible indication — this is one of the few accessibility questions where doing nothing is a pass. The checker reports it for review rather than as a failure, for two reasons: the default ring is drawn with no knowledge of your colours, so on a dark control it can be nearly invisible, and it is exactly one CSS reset away from being gone.
- My site is built with Tailwind. Will this just fail it?
- No, and this is the case that decides whether the checker is usable on the modern web at all. On a utility-first stack the indicator is composed on the element, not in the stylesheet — `focus:outline-none focus:ring-2` removes the ring and adds one back, in two separate class names sitting side by side in the markup — so the stylesheet alone genuinely cannot say whether a control ends up with an indicator. The checker recognises the escaped-colon class names those frameworks generate, plus the unprefixed `.outline-none` utility, and reports them as verify rather than failure. A hand-written `.btn:focus { outline: none }` in the same codebase is still a finding.
- Can you see my CSS?
- More of it than any other checker here. This one follows the stylesheets your page links, as long as they are served from your own domain, and reads the rules in them — because Focus Visible is a CSS criterion and a checker that only read <style> blocks would have nothing to say about almost any site. Stylesheets on a CDN or another origin are listed as not read rather than assumed innocent, so you can see the shape of what was left out.
- Why won't you fetch my CDN stylesheet?
- Because this endpoint takes a URL from anyone and fetches it, and following arbitrary third-party URLs out of that page would turn it into a general-purpose proxy pointed wherever a caller likes. Same-origin only is a deliberate restraint. The free scan does not have that constraint: it loads the page in a real browser, so every stylesheet applies and the computed style is what gets read.
- Does a keyboard user really notice?
- Try it for ten seconds on your own site. Click the address bar, then press Tab five or six times without touching the mouse, and say out loud where you are each time. With a ring, it is obvious. Without one, you are pressing Enter on something you cannot identify — on a page with a "Delete account" button somewhere in it, that is the actual stakes of the criterion.
- How is this different from your free scan?
- This reads source CSS for one page and answers instantly. The scan renders every page in a real browser, computes the styles the way a visitor's browser does, and checks all fifty level AA criteria, then gives you a report by criterion with the failing element on the page it is on. This checker is the ten-second version of one line of that report.
- Does this store my domain?
- No. The check is an HTTP GET of the page you name plus its own stylesheets, run inside the request and discarded when it answers. There is no account, no email field and no row written anywhere. If you go on to run the free scan, that one does keep a report so you can come back to it, which is the whole point of a report.
Keep reading
- Can a keyboard reach everything a mouse can? →
- Does your skip link actually work? →
- Does your colour pair pass 3:1? →
- Is your ARIA being thrown away? →
- What is your hamburger button called? →
- Which form fields have no label? →
- What does your heading outline look like? →
- Can a phone zoom your website? →
- The WCAG compliance tool →
- The ADA compliance checker →