Visible Focus Indicators
WCAG 2.2's enhanced focus requirements and CSS patterns that work everywhere
Overview
A visible focus indicator tells keyboard users where they are on the page. Without one, navigating by keyboard is like using a mouse with an invisible cursor. WCAG 2.4.7 has always required visible focus, but WCAG 2.2 raises the bar: 2.4.11 (Focus Not Obscured) requires that focused elements must not be entirely hidden behind sticky headers, footers, or other overlapping content.
WCAG Criteria:
- 2.4.7 Focus Visible — keyboard focus indicator must always be visible
- 2.4.11 Focus Not Obscured (Minimum) — focused element must not be entirely hidden by author-created content
Key requirements:
- Never use
outline: noneoroutline: 0without providing an alternative focus style - Use
outlinerather thanbox-shadowfor focus rings —outlinesurvives Windows High Contrast / forced-colors mode,box-shadowdoes not - Use
outline-offsetto create visual breathing room between the element and its focus ring - Use
:focus-visibleinstead of:focusso mouse users don’t see focus rings on click - Ensure sticky/fixed headers and footers don’t fully obscure focused elements — use
scroll-padding-toporscroll-padding-bottom - WCAG 2.4.11 is new in WCAG 2.2: focused elements must not be entirely hidden behind overlapping content
Focus Styles
Visible Focus Ring vs. Outline Removed
Try pressing Tab through these buttons — no focus indicator is visible.
View inaccessible code
<!-- outline:none removes the focus indicator entirely -->
<button style="outline: none;">Save</button>
<button style="outline: none;">Cancel</button>
<button style="outline: none;">Delete</button>Try pressing Tab through these buttons — a clear blue outline appears on focus.
View accessible code
<style>
.btn:focus-visible {
outline: 3px solid #005fcc;
outline-offset: 2px;
}
</style>
<button class="btn">Save</button>
<button class="btn">Cancel</button>
<button class="btn">Delete</button>What’s wrong with the bad example?
outline: nonecompletely removes the browser’s default focus indicator- Keyboard users pressing Tab through the buttons see no visual change — they have no idea which button is active
- This is one of the most common accessibility failures on the web, often added by CSS resets or design systems that suppress the “ugly” default outline
- Without focus visibility, keyboard users cannot operate the interface at all
Why outline and not box-shadow?
outlineis preserved in Windows High Contrast Mode (forced-colors).box-shadowis stripped entirelyoutline-offset: 2pxadds breathing room between the element and the ring, improving visibility:focus-visibleensures the ring only appears for keyboard navigation, not mouse clicks
What the keyboard user sees:
| Version | When pressing Tab |
|---|---|
Bad (outline: none) | Nothing — no visible indicator of which button has focus |
Good (:focus-visible outline) | A clear 3px blue ring appears around the focused button |
Focus Hidden by Sticky Header
Focus Obscured by Sticky Header vs. Scroll Padding
Tab through the links below. Some will scroll behind the sticky header.
View inaccessible code
<!-- Focused elements scroll behind the sticky header -->
<div style="position: relative; height: 200px; overflow-y: auto;">
<div style="position: sticky; top: 0; background: #1e293b;
color: white; padding: 12px 16px;">
Sticky Navigation Bar
</div>
<ul>
<li><a href="#">Link 1 — Introduction</a></li>
<li><a href="#">Link 2 — Getting Started</a></li>
<!-- ...more links... -->
<li><a href="#">Link 10 — License</a></li>
</ul>
</div>
<!-- When tabbing, focused links scroll behind the sticky header -->Tab through the links below. scroll-padding-top keeps focused items visible below the header.
View accessible code
<!-- scroll-padding-top keeps focused elements visible -->
<div style="position: relative; height: 200px; overflow-y: auto;
scroll-padding-top: 48px;">
<div style="position: sticky; top: 0; background: #1e293b;
color: white; padding: 12px 16px;">
Sticky Navigation Bar
</div>
<ul>
<li><a href="#">Link 1 — Introduction</a></li>
<li><a href="#">Link 2 — Getting Started</a></li>
<!-- ...more links... -->
<li><a href="#">Link 10 — License</a></li>
</ul>
</div>
<!-- scroll-padding-top offsets scroll so focus is never hidden -->What’s wrong with the bad example?
- The sticky header stays fixed at the top of the scrollable container
- As the user Tabs through links, the browser scrolls the focused element into view — but “into view” can mean directly behind the sticky header
- The focused element is technically in the viewport, but visually hidden under the opaque header
- This violates WCAG 2.4.11 Focus Not Obscured — the focused element must not be entirely hidden by author-created content
How scroll-padding-top fixes it:
scroll-padding-top: 48pxon the scroll container tells the browser to account for the sticky header height when scrolling elements into view- When the browser scrolls a focused element into view, it stops 48px below the top — exactly below the sticky header
- This is a pure CSS solution with no JavaScript required
What the keyboard user sees:
| Version | When tabbing through links in the list |
|---|---|
| Bad (no scroll padding) | Focused links scroll behind the sticky header — invisible |
Good (scroll-padding-top) | Focused links always appear below the sticky header |
Keep the criteria separate
- 2.4.7 Focus Visible (AA) requires a visible keyboard focus indicator.
- 2.4.11 Focus Not Obscured (Minimum) (AA) requires that an author-created sticky header, footer, banner, or overlay does not entirely hide the focused component.
- 2.4.13 Focus Appearance (AAA) defines a minimum indicator area and a 3:1 change of contrast. A 2px outline is a useful starting point, but it is not an automatic pass on every component and background.
Test while tabbing both directions, after anchor navigation and programmatic focus, at zoom, and with cookie banners, sticky footers, chat launchers, and other overlays present.