w3tweaks.com · CSS Tutorial

CSS :focus-visible

Keyboard-only focus rings · WCAG compliance · Two-tone patterns

Tab 1

:focus vs :focus-visible vs outline:none

Tab through elements in each column to see the difference. Then click the same elements with a mouse. The key behavior: :focus-visible shows a ring for keyboard, hides it for mouse.

⌨️ Press Tab to navigate — then try clicking with the mouse. Watch how each column behaves differently.
:focus — always shows
Link

Ring appears on both Tab key AND mouse click. Correct but can feel jarring for mouse users when clicking buttons.

button:focus {
  outline: 3px solid orange;
}
:focus-visible — keyboard only ✓
Link

Ring shows on Tab key. Hidden on mouse click. Best of both worlds — keyboard users see the ring, mouse users don't.

button:focus { outline: none; }
button:focus-visible {
  outline: 3px solid cyan;
}
outline:none — ❌ Never do this
Link

No ring ever. Tab through this column — you can't tell where focus is. Keyboard users are lost. This violates WCAG 2.4.7.

/* ❌ NEVER — breaks keyboard nav */
button:focus { outline: none; }
:focus-visible vs :focus-within — Not the Same
:focus-visible — styles the focused element

Applies to the element that is focused — only when focus was triggered by keyboard.

button:focus-visible {
  outline: 3px solid cyan;
}
/* button itself gets the ring */
:focus-within — styles a parent when child is focused

Applies to the parent when any descendant has focus. Used for form group highlighting.

.form-group:focus-within {
  border-color: cyan;
}
/* parent gets styled, not the input */
How :focus-visible decides to show: The browser uses a heuristic to determine if a visible focus indicator is necessary. It shows when: keyboard navigation (Tab, arrows) triggered the focus; the element is a text input (always shows — you need to know where you're typing); a script programmatically focused the element AND the element previously received keyboard focus. It hides when: a mouse or touch click triggered focus on a non-input element.
Tab 2

Focus Style Gallery — Tab Through Each

Eight different visual approaches. Tab through each button to see the keyboard focus indicator. All use :focus-visible so mouse clicks show nothing.

① Outline (recommended)
:focus-visible { outline: 3px solid cyan; outline-offset: 3px; }

The standard approach. outline respects High Contrast Mode. outline-offset creates breathing room.

② Outline + glow
:focus-visible {
  outline: 2px solid purple;
  box-shadow: 0 0 0 8px rgba(124,58,237,.55);
}

Outline for accessibility compliance + box-shadow for soft glow. The outline is what matters for HCM.

③ Box-shadow ring (use with caution)
:focus-visible {
  outline: none;
  box-shadow: 0 0 0 3px cyan, 0 0 0 5px rgba(6,182,212,.2);
}

Visually clean but box-shadow is invisible in Windows High Contrast Mode. Add a thin outline: transparent alongside.

④ Two-tone — any background ✓
:focus-visible {
  outline: 3px solid #000; ← always dark
  outline-offset: 2px;
  box-shadow: 0 0 0 5px #fff; ← white inner ring
}

The black outline + white inner ring is visible on any background. Tab through the buttons above — all look great even on gradient and image backgrounds.

⑤ Inset shadow
:focus-visible { box-shadow: inset 0 0 0 3px cyan; }

Focus ring inside the element. Works well for inputs and cards where an external ring would overlap adjacent elements.

⑥ Underline (links)
Tab to this link
a:focus-visible {
  outline: none;
  text-decoration-color: cyan;
  text-decoration-thickness: 3px;
  text-underline-offset: 3px;
}

For inline links, a styled underline can be a natural focus indicator. Combine with a background change for extra visibility.

⑦ Background change
:focus-visible {
  outline: none;
  background: rgba(6,182,212,.15);
  color: cyan;
}

Works for nav items and menu links where an outline would look out of place. Must be clearly distinguishable from the hover state.

⑧ Animated offset
:focus-visible { outline: 3px solid cyan; outline-offset: 3px; transition: outline-offset .15s; }
@media (prefers-reduced-motion: reduce) { :focus-visible { transition: none; } }

Subtle animation makes the ring appear to "expand" from the element. Always disable with prefers-reduced-motion.

Tab 3

Real Patterns & WCAG Compliance

Production patterns and the WCAG 2.2 requirements for focus indicators.

Skip Link (Tab to activate)
.skip-link {
  position: absolute;
  top: -100%; /* visually hidden */
  left: 8px;
  transition: top .15s;
}
.skip-link:focus { top: 0; } /* use :focus not :focus-visible — keyboard and programmatic */
⚠️ CSS Reset Gotcha
/* ❌ Many resets include this — breaks everything */
*:focus { outline: none; }

/* ❌ Some Bootstrap versions did this */
/* ❌ Various legacy reset files inherited the pattern */

/* ✅ Fix: always check your reset file */
*:focus { outline: revert; } /* restore defaults */

Audit your CSS reset. Many include *:focus { outline: none } or *:focus { outline: 0 } that silently kills keyboard navigation site-wide.

🔄 The Safe Fallback Pattern
/* Works everywhere */
button:focus {
  outline: 3px solid #06b6d4;
  outline-offset: 2px;
}

/* Remove for mouse — where supported */
button:focus:not(:focus-visible) {
  outline: none;
}

The progressive enhancement pattern: apply focus styles broadly, then remove for mouse using :not(:focus-visible). Old browsers that don't support :focus-visible still show focus.

🎨 High Contrast Mode (forced-colors)
/* Standard focus style */
:focus-visible {
  outline: 3px solid #06b6d4;
}

/* High Contrast Mode override */
@media (forced-colors: active) {
  :focus-visible {
    outline: 3px solid CanvasText;
    /* CanvasText = system high-contrast text color */
  }
}

In Windows High Contrast Mode, custom colors are replaced by system colors. CanvasText is the high-contrast foreground. outline (but NOT box-shadow) is respected by forced-colors.

✅ WCAG 2.2 Compliant Focus
/* Meets WCAG 2.4.11 (AA in 2.2) */
:focus-visible {
  outline: 3px solid #005fcc; ← ≥2px thick
  outline-offset: 2px; ← ≥1px offset
  /* #005fcc on white = 4.7:1 ≥ 3:1 */
}

/* Or the universal two-tone approach */
:focus-visible {
  outline: 3px solid #000;
  outline-offset: 2px;
  box-shadow: 0 0 0 5px #fff;
}

WCAG 2.4.11 (Focus Appearance, Level AA in WCAG 2.2) requires: minimum 2px thickness, 3:1 contrast ratio. The two-tone black+white pattern exceeds this universally.

WCAG Focus Requirements Reference
CriterionLevelRequirementCSS pattern
2.4.7 Focus VisibleAAKeyboard focus must be visible:focus-visible { outline: 3px solid … }
2.4.11 Focus AppearanceAA≥2px thick, ≥3:1 contrastoutline: 3px + high-contrast color
2.4.13 Focus AppearanceAAAEnhanced size + contrastTwo-tone outline pattern
1.4.11 Non-text ContrastAAUI component states: ≥3:1Focus color vs bg: check ratio
European Accessibility Act (EAA) — effective June 2025: The EAA mandates WCAG 2.1 Level AA minimum for products/services across EU member states. This includes WCAG 2.4.7 (Focus Visible) as a hard requirement. WCAG 2.2's updated 2.4.11 (Focus Appearance) criteria are being adopted by national implementations. If you serve EU users, keyboard focus indicators are a legal requirement, not a nice-to-have.
Read the tutorial