WCAG 2.4.7 · Level AA · WCAG 2.0
Focus Visible
Every keyboard-operable component must have a visible focus indicator. Removing the outline with CSS and providing no replacement fails.
- Principle
- Operable
- Guideline
- Navigable
- Level
- AA
- Added in
- WCAG 2.0
What it really means
Every keyboard-operable control must have a visible focus indicator. When
you Tab through a page, you should always be able to answer "where am I
right now?" within a glance. This is the rule that design teams
accidentally break when they outline: none away the browser default and
forget to replace it.
WCAG 2.2 also introduces 2.4.13 Focus Appearance (AAA) which tightens the bar: indicators must be at least 2 CSS pixels thick and have 3:1 contrast against the unfocused state. The AA rule below only requires that an indicator exists.
Who it helps
- Keyboard-only users, full stop. Without a visible indicator, they are typing blind.
- Screen-magnifier users, who have a small viewport and need a clear signal to know what scrolled into view.
- Everyone using keyboard shortcuts — anyone who lives in Tab, Cmd-K, Escape.
How to test
- Tab through the entire page. Every stop should have a visible indicator.
- Test on every background in the design system — dark sections, brand accents, images, modals. Focus rings often break on coloured surfaces.
- Run axe DevTools —
focus-order-semanticsflags focus stops on unlabelled widgets; visual focus is harder to automate so pair with manual review.
A failing pattern
/* The single worst line in the a11y world. */
*:focus { outline: none; }
/* Replacement indicator exists but only on hover — keyboard users lose. */
button:hover { box-shadow: 0 0 0 2px #3b82f6; }A passing pattern
The GOV.UK pattern — used across the FasterForward family — is a two-layer indicator that survives on light and dark backgrounds:
:focus-visible {
outline: 3px solid #ffd700; /* yellow ring */
outline-offset: 2px;
box-shadow: 0 0 0 6px #1e293b; /* dark inset, doubles contrast */
}Yellow on light backgrounds reads as a caution cue; the dark inset gives the ring a high-contrast edge on light or dark surfaces. You can fine-tune the ring colour to your brand — just keep the contrast.
Use :focus-visible, not :focus. :focus-visible is the modern
heuristic that only shows the ring for keyboard/AT users, hiding it on
plain mouse clicks. This was landed in all evergreen browsers by 2022.
For custom widgets, roving tabindex usually moves a single tab stop through a group (tabs, menu, grid). Render the indicator on the active item:
<div role="tablist">
{tabs.map((tab, i) => (
<button
key={tab.id}
role="tab"
aria-selected={i === active}
tabIndex={i === active ? 0 : -1}
onFocus={() => setActive(i)}
>
{tab.label}
</button>
))}
</div>Notes and edge cases
- Skip links hidden until focused — still count. Make them visually appear at the top-left when they take focus.
- Clip-path / sr-only elements that receive focus by mistake don't
have a meaningful indicator. Guard them with
tabindex={-1}unless you want focus there. - Composed controls — if a label and input are both focusable, one of them should be the focus target, not both.
Related rules
- WCAG 2.4.11 Focus Not Obscured (Minimum) — introduced in 2.2: the focus indicator cannot be entirely hidden by sticky headers or cookie banners.
- WCAG 2.4.13 Focus Appearance — AAA: 2px perimeter, 3:1 contrast against unfocused state.
- axe: focus-order-semantics.