Skip to content

notes 5 min accessibility

note

Where the focus ring went, and how to find out

For somebody using a keyboard the focus ring is the cursor. outline: none deletes it in one line, usually to fix a complaint about mouse clicks - and a page with no visible focus is a page that cannot be used without a mouse.

Press Tab on most sites and watch where you are. On a good one a clear ring moves from link to link. On a surprising number nothing visible happens at all: focus is moving, the browser knows where it is, and the reader does not.

The cause is nearly always the same declaration, written for a reason that made sense at the time:

the line, in its three usual forms
:focus { outline: none; }
button:focus { outline: 0; }
* { outline: none !important; }

Somebody clicked a button, saw a ring appear around it, and filed it as a visual bug. Older reset stylesheets shipped the first form outright, with a comment asking the author to define their own focus styles - and the comment was the part that did not survive.

What :focus-visible changed #

The complaint was legitimate: :focus matches however focus arrived, so a mouse click on a button drew the same ring as a Tab. :focus-visible exists to separate the two. It matches when the browser judges that a focus indicator would help, and the judgement is a heuristic rather than a rule:

Keyboard navigation
Always matches. Tab, Shift+Tab and arrow keys inside a widget all draw the ring.
A mouse click on a button or link
Usually does not match. This is the case people were removing outlines to suppress.
A text field
Matches however it was focused, because the reader is about to type and needs to know where.
Focus moved by script
Follows the last interaction. A dialog opened by a key press shows the ring on its first control; one opened by a click usually does not.

Every current browser's own stylesheet now draws its default ring on :focus-visible rather than on :focus. The mouse-click ring that outline: none was written to remove is no longer drawn by default. On a page built today, the line removes nothing anyone complained about and everything a keyboard user needs.

A focus style that survives #

a ring that shows for the keyboard and holds up in forced colours
:focus-visible {
  outline: 2px solid currentColor;
  outline-offset: 2px;
}

/* A custom ring drawn with box-shadow needs a transparent
   outline beside it: forced-colours mode drops shadows
   and paints the outline in the system colour instead. */
.btn:focus-visible {
  box-shadow: 0 0 0 3px var(--ring);
  outline: 2px solid transparent;
}
  • Outline does not take up space. Changing it never shifts the layout, which is why it is the right property for something that appears and disappears on every key press.
  • It follows border-radius in every current engine, so the old excuse that it drew a square box around a rounded button no longer holds.
  • outline-offset keeps it off the control. A ring drawn flush against a dark button in a dark colour is technically present and practically invisible.
  • Contrast applies to it. WCAG asks for a visible focus indicator at level AA, and non-text contrast puts that indicator at 3:1 against what is next to it.

The other ways focus disappears #

Removing the outline is the common case. These are the ones that survive a search of the stylesheet for outline:

Clipped by an ancestor
An outline is drawn outside the element's box. A parent with overflow: hidden cuts off whatever falls outside it, so a ring around a card in a carousel can be clipped down to nothing.
The same colour as the background
A ring in the brand colour on a section painted in the brand colour. Present in the computed style, absent on the screen.
Behind a sticky header
Focus moves to a link the page has scrolled underneath a fixed bar. WCAG 2.2 added a criterion for exactly this: the focused element must not be entirely hidden by content the author put on top of it.
Never focusable at all
A div with a click handler is not in the tab order unless it has tabindex. There is no ring to remove because there was never a stop.
Focus that goes nowhere visible
A closed menu whose links are hidden with opacity: 0 rather than display: none still receives focus. The reader presses Tab and the ring vanishes into an invisible panel for several stops.

Auditing focus on a page you did not build #

Pressing Tab is the right first test and a poor complete one: past twenty stops nobody remembers what they saw at the fourth. A listener can report what each stop actually drew:

log every focus stop and whether it drew anything
document.addEventListener('focusin', (event) => {
  const el = event.target;
  const s = getComputedStyle(el);
  const ring = s.outlineStyle !== 'none' && parseFloat(s.outlineWidth) > 0;
  const shadow = s.boxShadow !== 'none';
  console.log(ring || shadow ? 'drawn' : 'NOTHING', el,
    s.outlineStyle, s.outlineWidth, s.boxShadow);
});

// Then press Tab through the page. A NOTHING row is not
// proof of a failure - a focus style may change the
// background instead - but every one deserves a look.
  1. Walk the whole page by keyboard, including any menu, dialog and cookie banner. Traps live in the components bolted on last.
  2. Read each stop for a visible change, not only for an outline. A background or underline change can be a valid indicator if it has the contrast.
  3. Check the order against the layout. A stop that jumps across the page and back is usually a positive tabindex somebody added to fix something else.
  4. Repeat with forced colours on. Rings built from box-shadow alone vanish there, and that is the mode many keyboard users rely on. Preferences says whether the page has any forced-colors rules at all.

Focus path draws the tab route over the page as numbered arcs, with the contrast of each focus ring, so a backwards hop and an invisible stop are both visible at once. Keyboard walk steps through every stop itself and keeps a log of what took focus and whether it was shown, which is the record a fix can be checked against afterwards.

The cheapest audit there is: unplug the mouse for five minutes. If you cannot tell where you are, neither can anyone who has no choice about the mouse.

Written by Ján Turský

Building LoupeKit and other browser tools out of Bratislava, under Apptiary.

run it here, no install

tools in this note

SAVE 33% €74.99 €49.99 Lifetime See the offer