Skip to Content
Component ExamplesFocus Indicators

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
<!-- 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>
Live Preview

Try pressing Tab through these buttons — no focus indicator is visible.

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

Try pressing Tab through these buttons — a clear blue outline appears on focus.

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
<!-- 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
<!-- 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 -->
Live Preview

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

Resources