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
<!-- 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 — no focus indicator is visible.
<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>Try pressing Tab through these buttons — a clear blue outline appears on focus.
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
<!-- 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. Some will scroll behind the sticky header.
<!-- 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 -->Tab through the links below. scroll-padding-top keeps focused items visible below the header.
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 |