# Focus rings too thin or faint to spot

Source: https://easeweb.dev/learn/focus-appearance
Topics: Keyboard and focus > The focus indicator is large and high in contrast (WCAG 2.4.13)
The fix: Make the indicator at least 2 CSS pixels thick and give its changed pixels 3:1 against what they replace. Either use a solid 2px ring coloured for each surface, a two-tone ring that works on any surface, or a fill change that swaps the button's colours on focus.
Test with: contrast measurement, keyboard, zoom, forced colors
References: https://www.w3.org/WAI/WCAG22/Understanding/focus-appearance.html https://www.w3.org/WAI/WCAG22/Techniques/css/C40 https://developer.mozilla.org/en-US/docs/Web/CSS/:focus-visible

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.

## What happens

Tab through a row of blue buttons on a white page, then through the same buttons on a dark navy banner. Each button gets a focus ring, so focus is technically visible. But the ring is a single pixel of pale blue. On the white page it almost disappears; on the banner it shows, but as a hairline you have to look for.

## Why it fails

Success criterion 2.4.13 Focus Appearance (AAA) asks two things of an indicator the author has styled. It must cover at least the area of a 2 CSS pixel thick ring around the unfocused control. And the pixels that change must have at least 3:1 contrast between their focused and unfocused colours. The ring here is 1 pixel thick, so it fails the area test on every surface. On the white page, `#a8c7fa` against `#ffffff` is 1.72:1, so it fails the contrast test too.

This is a stricter test than 2.4.7, which only asks that focus be visible, and than 1.4.11, which asks for 3:1 against adjacent colours. The 2.4.13 contrast is a change of state: compare each pixel with what it was before focus.

## Who is affected

Anyone with a dim or glary display is affected too.

## What automation and AI miss

Most scanners only check that focus changes something, which this ring does. Measuring the 2.4.13 area needs the control's perimeter and the indicator's thickness; measuring its contrast needs the before and after colours of the same pixels, on each surface. Generated code tends to copy a framework's thin default ring, or add `box-shadow` alone, which forced colors removes.

## Before: a hairline ring

```css
.brand-button:focus-visible {
  outline: 1px solid #a8c7fa;
  outline-offset: 2px;
}
```

The ring is half the required thickness, and on a white page its pale blue is 1.72:1 against the white it replaces.

## After: a solid 2px ring for each surface

```css
.brand-button:focus-visible {
  outline: 2px solid #1a5fb4;
  outline-offset: 2px;
}
/* On the navy banner, the blue would be 2.45:1, so switch to white. */
.banner .brand-button:focus-visible {
  outline-color: #fff;
}
```

The ring is 2 pixels thick and sits outside the button, so its perimeter is larger than the button's and its area is more than enough. On the white page, `#1a5fb4` replaces white at 6.29:1; on the banner, white replaces `#0b2545` at 15.39:1. This holds only on the surfaces you have set a colour for: each new background needs its own check and, often, its own rule.

## After: a two-tone ring for every surface

```css
.brand-button:focus-visible {
  outline: 2px solid #fff;
  outline-offset: 2px;
  /* Dark on both sides of the white ring */
  box-shadow: 0 0 0 6px #1a1a1a;
}
```

The white outline sits between two dark bands from the shadow. On a light page the dark bands change white to `#1a1a1a`, at 17.40:1, and together they are thicker than 2 pixels. On a dark surface the white ring itself changes navy to white, at 15.39:1. One rule covers every surface. Forced colors drop the shadow but keep the outline.

## After: a fill change, for designs without a ring

```css
.brand-button {
  border: 2px solid #1a5fb4;
  background: #1a5fb4;
  color: #fff;
}
.brand-button:focus-visible {
  background: #fff;
  color: #1a5fb4;
  /* Shows only in forced colors, where backgrounds are replaced. */
  outline: 2px solid transparent;
}
.banner .brand-button {
  border-color: #fff;
  background: #fff;
  color: #0b2545;
}
.banner .brand-button:focus-visible {
  background: #0b2545;
  color: #fff;
}
```

On focus the whole button swaps its colours, so the indicator's area is the entire inside of the button, far more than a 2-pixel ring. The fill changes from `#1a5fb4` to white, at 6.29:1, or on the banner from white to `#0b2545`, at 15.39:1. The border keeps the button's shape when its fill matches the page. The transparent outline is drawn in a system colour in forced colors, where the fill change would vanish.

## Which option to use

Each of the three passes 2.4.13. The ring per surface matches the brand closely but needs a colour and a check for every background. The two-tone ring needs one rule for any surface, including user themes and editor-chosen colours, and looks heavier. The fill change suits designs that don't want a ring, but every button style needs its own pair of colours, and the focused state must not look like the hover or pressed state. Choose by how many surfaces your buttons sit on and how much the design can change.

## Implementation decisions

The 2-pixel rule is an area, not a stroke width. An outline outside the control has a longer perimeter than the control, so 2 pixels is enough. An inset indicator, drawn inside the edge, has a shorter perimeter and needs to be thicker to cover the same area. A ring with gaps, dashes or rounded corners also covers less; measure it rather than assuming.

Only the pixels that change count. A ring that is half on the button and half on the page needs 3:1 against each colour it replaces. A grey ring that differs from the page by 3:1 but sits on a grey card may not.

`overflow: hidden` on a parent can cut off part of an outside ring. Leave space around controls, or use `outline-offset` with a negative value and a thicker ring. The criterion's exception covers focus indicators that the browser draws and the author has not changed. Relying on the browser default is allowed, but browsers draw different rings, and many sites override them anyway.

Put the ring in a design token or a shared component style, so a theme change cannot quietly thin it again.

## Verify the fix

1. Inspect the focused control's computed styles and confirm the indicator is at least 2 CSS pixels thick, or covers an area at least that large.
2. With a contrast checker, compare the indicator's colour with what it replaces, on each surface, and confirm at least 3:1.
3. Tab through every control on every real background, in both directions, and confirm focus can be found at a glance.
4. Zoom to 200% and use a screen magnifier, and confirm the indicator is not clipped and still reads as a ring or a fill.
5. Turn on forced colors and confirm the focused control is still outlined.

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

2.4.13 is a AAA criterion, so most conformance targets don't require it. It is still the clearest test of whether focus is easy to see, and a ring that passes it also passes 2.4.7 and the focus part of 1.4.11. The colours here are examples; measure your own. Test the complete journey on your real surfaces.
