Skip to content

Keyboard focus disappears on a branded button

A reset removes the outline and leaves no visible replacement.

WCAG 2.4.7 Test with KeyboardScreen readerZoomForced colors Markdown for agents
Broken Focused. You can't tell.
Fixed Focused. A ring shows it.
The same control broken and fixed.
01

The problem

Keyboard users can't see where they are on the page.

What happens Focus moves, but nothing shows it. Read moreRead less

Tab across a row of actions on a branded surface. Each control receives focus, but only the pointer hover state changes its appearance. The keyboard position is invisible.

Why it fails outline: none, with no replacement. Read moreRead less

The stylesheet sets outline: none. There is no focus-visible rule that works on both the light page and the dark banner.

Who is affected Keyboard users, guessing what Enter will press. Read moreRead less

Keyboard users must guess which action Enter will trigger. This is especially confusing around destructive or repeated controls.

What automation and AI miss The code looks correct. The screen does not. Read moreRead less

DOM semantics can be correct while the focused appearance remains unusable. A scanner may not exercise every background or clipping container. A generated outline color may work on white and disappear on a branded surface.

02

Try it

Press Tab and watch the buttons.

Press Tab to move through the buttons on both backgrounds.

A recreated demo. The broken version is intentionally inaccessible.

03

The fix

Delete the reset. Add a visible ring for each surface.

  1. 1Use :focus-visible, so the ring shows for keyboard focus only.
  2. 2At least 3:1 contrast against the surface it sits on.
  3. 3Keep an outline in forced colors, not only a shadow.
Before A reset with nothing in its place
*:focus {  outline: none;}.brand-button:hover {  background: var(--brand-dark);}
After A ring for each surface
.brand-button:focus-visible {  outline: 3px solid var(--focus-ring);  outline-offset: 3px;}/* On the dark banner, switch to a ring that contrasts with it. */.banner .brand-button:focus-visible {  outline-color: var(--focus-ring-on-dark);}@media (forced-colors: active) {  .brand-button:focus-visible {    outline-color: Highlight;  }}
Why this works

The reset removes the browser's indicator from every control, and nothing replaces it. The hover style responds only to a pointer, so a keyboard user sees no change at all.

Delete the reset rather than overriding it control by control, then add a visible indicator.

:focus-visible shows the ring when focus comes from the keyboard without adding it to every mouse click. The ring needs a contrast ratio of at least 3:1 against the surface it sits on. One color rarely works on both a light page and a dark banner, so set one per surface, or use a two-tone ring that stays visible on either.

Implementation decisions

An outline can be clipped by overflow: hidden. Leave enough space around controls or revise the clipping container. A box shadow alone may disappear in forced colors, so keep an outline even if you add a shadow. Sticky headers and cookie banners can cover the focused control; add scroll-padding-top for sticky content.

Do not infer conformance from a single screenshot. Inspect focused states in the actual page, on every background the control appears on.

04

Verify the fix

4 checks, no mouse.

0 of 4 checked

Record the OS, browser and assistive-technology versions, the build, the date, and the actual result of each step. Repeat after the shared theme or component styles change.

Limits

This is a starting pattern, not a guarantee for every application. Token names in the snippets are placeholders for your own design tokens. Test the complete journey on your real surfaces.

This is a recreated teaching example, not a finding about a named client. The code is a starting pattern: test it in your own product. No completed assistive-technology test is claimed here.

References: www.w3.org

Keep this fixed as your code changes.

Turn guides like this into rules your coding agents and CI follow, then have the result retested with a keyboard and screen readers.

How retests work