Skip to Content
Component ExamplesFocus Indicators

Visible Focus Indicators

WCAG 2.2's enhanced focus requirements and CSS patterns that work everywhere

Maintained by Martin KrugerLast reviewed

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:

Key requirements:

  • Never use outline: none or outline: 0 without providing an alternative focus style
  • Use outline rather than box-shadow for focus rings — outline survives Windows High Contrast / forced-colors mode, box-shadow does not
  • Use outline-offset to create visual breathing room between the element and its focus ring
  • Use :focus-visible instead of :focus so mouse users don’t see focus rings on click
  • Ensure sticky/fixed headers and footers don’t fully obscure focused elements — use scroll-padding-top or scroll-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

Inaccessible

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>
Accessible

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: none completely 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?

  • outline is preserved in Windows High Contrast Mode (forced-colors). box-shadow is stripped entirely
  • outline-offset: 2px adds breathing room between the element and the ring, improving visibility
  • :focus-visible ensures the ring only appears for keyboard navigation, not mouse clicks

What the keyboard user sees:

VersionWhen 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

Inaccessible
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 -->
Accessible
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: 48px on 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:

VersionWhen 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.


Resources