Animated Focus Indicators and WCAG Focus Criteria
Part of WCAG Animation Conformance in Accessible Motion Architecture.
The problem
A design system adds polish to focus: the ring fades in over 150ms and expands from the control’s edge. It looks refined in a demo. A keyboard user tabbing quickly through a form sees a sequence of half-drawn rings and, at speed, effectively nothing — each ring is still fading in when focus moves on. On another screen, a sticky footer animates up and covers the focused button in a long form.
Focus indication is a requirement with timing implications, and animation is where those implications are usually missed.
Root cause analysis: three criteria, one rule of thumb
2.4.7 Focus Visible (AA) requires that the keyboard focus indicator is visible. An indicator that has not finished appearing when focus moves is not meaningfully visible.
2.4.11 Focus Not Obscured (Minimum) (AA, WCAG 2.2) requires that the focused component is not entirely hidden by author-created content. Animated overlays, sticky headers and footers that move over content are exactly the kind of author-created content that can obscure focus.
2.4.13 Focus Appearance (AAA, WCAG 2.2) sets out how substantial the indicator must be: an area at least as large as a 2 CSS pixel perimeter around the component, and a contrast ratio of at least 3:1 between focused and unfocused states.
The practical rule that satisfies all three: the indicator itself appears instantly and does not move; anything that animates is decoration around an already-visible indicator.
Two further details matter for animated interfaces. :focus-visible applies the indicator only when the browser judges a keyboard-like interaction, which keeps rings off mouse clicks without removing them from keyboard users. And focus during transitions — a panel animating in, a route transition running — must still show an indicator; if focus moves into content that is mid-animation, the ring must be visible on it immediately, as in focus management during route transitions.
Step-by-step resolution
Production code pattern
/* The indicator: instant, substantial, high contrast. */
:where(a, button, input, select, textarea, [tabindex]):focus-visible {
outline: 3px solid var(--color-focus); /* >= 2px perimeter for 2.4.13 */
outline-offset: 2px;
transition: none; /* never fade or grow the ring */
}
/* Decoration: animates around an already-visible ring. */
.button {
position: relative;
isolation: isolate;
}
.button::before {
content: "";
position: absolute;
inset: -6px;
z-index: -1;
border-radius: inherit;
background: var(--color-focus-tint);
opacity: 0;
transition: opacity var(--duration-100) linear;
}
.button:focus-visible::before { opacity: 1; }
/* Focus must not end up behind sticky UI. */
:root {
scroll-padding-block: calc(var(--header-h) + 8px) calc(var(--footer-h) + 8px);
}
/* Focused controls stay above animated overlays that do not need to cover them. */
.sticky-footer { z-index: 10; }
.form :focus-visible { position: relative; z-index: 11; }
@media (prefers-reduced-motion: reduce) {
.button::before { transition: none; } /* tint appears instantly; ring unchanged */
}
// Verify quickly: tab through and log any focused element whose ring could be obscured.
document.addEventListener('focusin', (e) => {
const r = e.target.getBoundingClientRect();
const hiddenTop = r.top < parseFloat(getComputedStyle(document.documentElement).scrollPaddingBlockStart);
const hiddenBottom = r.bottom > innerHeight - parseFloat(getComputedStyle(document.documentElement).scrollPaddingBlockEnd);
if (hiddenTop || hiddenBottom) console.warn('Focused element may be obscured:', e.target);
});
Rendering Impact: paint for the outline, composite for the decorative tint. Removing the transition from the outline also removes a per-frame paint of the ring during rapid tabbing.
:where() keeps the focus rule’s specificity at zero, so component styles can extend it without having to out-specify it — and, importantly, cannot accidentally remove it by setting outline: none at a lower specificity.
Focus and animated overlays
2.4.11 is where animation most often causes a failure that nobody notices in review. A sticky footer that slides up when a form is edited, a chat widget that expands, a cookie banner that animates in — each can cover the control the user is focused on, and because keyboard users are the ones affected, a mouse-driven review will not reveal it.
Two defences cover most cases. scroll-padding on the scroll container keeps browser-driven focus scrolling clear of fixed UI, so tabbing to a control below a sticky footer scrolls it into the visible region rather than behind it. And giving focused controls a stacking position above non-essential overlays means a decorative panel cannot cover them. Overlays that must cover content, such as modals, should also be moving focus into themselves, which resolves the question differently.
Verification checklist
Constraints and trade-offs
- An instant ring is less “designed” than a fading one; decoration around it can carry the polish.
- High-contrast indicators can clash with brand colours; contrast is the requirement, not the palette.
scroll-paddingaffects all scrolling into view, including anchor links, which is usually desirable.- Raising focused elements in the stacking order can change how overlapping components render.
- Windows High Contrast and forced-colours modes override indicator colours; test there too.
Frequently asked questions
Can a focus ring fade in?
No. The indicator must be visible when focus arrives; a fade means it is incomplete or invisible during rapid tabbing.
What may animate on focus, then?
Decoration around an already-visible ring: a background tint, a shared highlight that glides between items, or a small scale on the control.
What does 2.4.11 require?
That the focused component is not entirely hidden by author-created content, which includes animated overlays, sticky headers and footers.
How big should a focus indicator be?
At AAA, at least the area of a 2 CSS pixel perimeter around the component, with a contrast ratio of at least 3:1 between focused and unfocused states.
Related
- WCAG Animation Conformance — the criteria in context
- Accessible Focus-Ring Animation Patterns — implementation patterns
- Focus Highlights That Follow Anchors — gliding decoration done safely