Accessibility Recipe No. 01
The two-tone focus ring
One focus style that keeps at least 3:1 contrast on every solid background. Here is the recipe, the maths that makes it work, and the five places it still fails.
Updated October 2026Reviewed by Khushwant Parihar, CPACC
- Serves
- Any solid background
- Prep
- Two CSS declarations
- Pairs with
- 1.4.11 Non-text Contrast, 2.4.7 Focus Visible, 2.4.13 Focus Appearance (AAA)
Taste test: Tab across twelve backgrounds
Press Tab to walk the wall. Start with the single blue ring, the style most sites ship, including this one. It reaches 3:1 on only 3 of the 11 solid colours: Paper, Ink and Sunflower. On Focus blue it is gone entirely. Then switch to the two-tone ring and walk it again.
Each tile lists both rings' contrast against it. The minimum for a focus indicator is 3:1.
- Paper#ffffff
- Blue ring
- 5.16:1 passes
- Two-tone
- 21.00:1 passes
- Ink#000000
- Blue ring
- 4.06:1 passes
- Two-tone
- 21.00:1 passes
- Tie-break grey#767676
- Blue ring
- 1.13:1 fails
- Two-tone
- 4.62:1 passes
- Focus blue#2563eb
- Blue ring
- 1.00:1 fails
- Two-tone
- 5.16:1 passes
- Navy#1e3a8a
- Blue ring
- 2.00:1 fails
- Two-tone
- 10.35:1 passes
- Sunflower#facc15
- Blue ring
- 3.37:1 passes
- Two-tone
- 13.71:1 passes
- Tomato#ef4444
- Blue ring
- 1.37:1 fails
- Two-tone
- 5.58:1 passes
- Mint#34d399
- Blue ring
- 2.68:1 fails
- Two-tone
- 10.92:1 passes
- Violet#7c3aed
- Blue ring
- 1.10:1 fails
- Two-tone
- 5.69:1 passes
- Tangerine#f97316
- Blue ring
- 1.84:1 fails
- Two-tone
- 7.49:1 passes
- Charcoal#1f2937
- Blue ring
- 2.84:1 fails
- Two-tone
- 14.67:1 passes
- Fadegradient
Not one solid colour, so no guarantee: check this one by eye.
Why it can't lose
Every colour has a contrast ratio against white and another against black. Multiply the two and you always get 21, the contrast between white and black themselves.
If two numbers multiply to 21, they cannot both be small. The worst case is when they are equal, at the square root of 21: 4.58:1. So on any solid background, the better band of a black and white ring has at least 4.58:1, comfortably over the 3:1 a focus indicator needs.
The working
against white = 1.05 ÷ (L + 0.05)
against black = (L + 0.05) ÷ 0.05
white × black = 1.05 ÷ 0.05 = 21
worst case = √21 ≈ 4.58
L is the background's relative luminance, from 0 for black to 1 for white. It cancels out, which is the whole trick.
Now try to break it. Drag the sliders, type any hex colour, or load a tile from the wall. The single blue ring fails all over the place. The two-tone ring never drops below 3:1, and the last line never moves off 21.
Both buttons are drawn as if focused. You can also Tab to them.
- Background
- #2563eb
- Blue ring against it
- 1.00:1Fail
- White band against it
- 5.16:1
- Black band against it
- 4.06:1
- Better band
- 5.16:1Pass
- White × black
- 21.00
The recipe
Ingredients
- One
outlinefor the inner band. - One
box-shadowfor the outer band, reaching 2px further. - Two colours at least 9:1 apart. Black and white are 21:1.
Method
- Put it on
:focus-visible, so keyboard focus always shows it and a mouse click does not. - Keep
outline-offset: 0. Both bands then sit on the background outside the component, which is the only place W3C's guarantee covers. - Make each band at least 2px thick. That also meets the size test in 2.4.13 Focus Appearance.
- Never write
outline: noneto swap in a shadow-only ring. Forced-colours mode removes shadows, and the ring with them.
/* The two-tone focus ring (W3C technique C40) */
:focus-visible {
/* Inner band. An outline survives forced-colours mode. */
outline: 2px solid #fff;
/* Both bands sit on the background, outside the component. */
outline-offset: 0;
/* Outer band: 2px of black around the white. */
box-shadow: 0 0 0 4px #000;
}Serving notes
- Components that already use
box-shadowfor elevation lose it on focus. List both shadows, ring first. - A global rule loses to component styles. Search the codebase for
outline: none,outline: 0and Tailwind'soutline-none. - Outlines follow border-radius in current browsers, so the ring hugs pills and rounded cards.
/* A card that already has a shadow keeps it */
.card:focus-visible {
box-shadow: 0 0 0 4px #000, 0 8px 24px rgb(0 0 0 / 0.15);
}Make it on-brand: the 9:1 rule
Black and white are not the only pair. W3C's technique C40 gives the general rule: if the two colours contrast at least 9:1 with each other, at least one of them reaches 3:1 against any solid background.
It is the same maths. The background that hurts most sits halfway between the two colours, where each band gets the square root of the pair's contrast. The square root of 9 is 3. Pick a pair below and the builder shows that worst background and writes the CSS.
The two bands contrast with each other at
10.35:1
Guaranteed. That is at least 9:1, so on any solid background one band reaches at least 3.21:1.
Your ring on its worst background, #8f8f8f
This grey sits halfway between your two colours, so it is the background the pair handles worst.
/* Two-tone focus ring: #ffffff and #1e3a8a (10.35:1 apart) */
:focus-visible {
outline: 2px solid #ffffff;
outline-offset: 0;
box-shadow: 0 0 0 4px #1e3a8a;
}When the recipe won't save you
The recipe fixes one problem: a focus indicator without enough contrast. These five survive it. Each one has a broken and a fixed version to try with the keyboard.
Failure 01 · 2.4.11 Focus Not Obscured (AA)
It is hiding under a sticky header
A perfect ring is no help if the focused control is under something. Tab to the bottom of each list, then press Shift and Tab to come back up. In the broken list each item slides under the sticky bar. The fixed list adds scroll-padding-top equal to the bar's height, so the browser stops scrolling before the bar.
Broken
Fixed
Failure 02 · 1.4.11 Non-text Contrast and 2.4.7 Focus Visible (AA)
Its container cuts it off
The ring is drawn outside the button, so a parent with overflow: hidden clips it. Both buttons below show the ring as if focused. The fix is room: padding on the container at least as wide as the ring (4px here).
Broken
A card with overflow: hidden and a full-width button.
Fixed
A card with overflow: hidden and a full-width button.
Failure 03 · 2.4.7 Focus Visible (AA)
Forced-colours mode deletes half of it
Windows high contrast mode and other forced-colours modes remove every box-shadow. A ring made only of shadows vanishes completely. The recipe keeps one band as an outline, which survives in the user's own colours. The switch below imitates the mode; to test the real thing, use Chrome DevTools, Rendering, Emulate CSS media feature forced-colors.
Broken
Fixed
Failure 04 · 2.4.7 Focus Visible (AA)
Focus lands on something invisible
Custom checkboxes usually hide the real input and draw a box next to it. Focus goes to the hidden input, so the ring is drawn around nothing. Tab to each checkbox. The fix moves the ring to the visible box with input:focus-visible + .box.
Broken
Fixed
Failure 05 · 2.4.7 Focus Visible (AA)
Focus never reaches the option
In a listbox or combobox built with aria-activedescendant, keyboard focus stays on the container and the arrow keys only change which option is active. The CSS ring therefore circles the whole list. Tab into each list and use the arrow keys: the broken one marks the active option with a pale tint, the fixed one gives it the two-tone ring too.
Broken
Pick a herb (broken)
- Basil
- Coriander
- Mint
- Parsley
Fixed
Pick a herb (fixed)
- Basil
- Coriander
- Mint
- Parsley
Taste it before you serve it
- Tab through every page template with the keyboard alone. Every stop shows the ring.
- Search for outline: none, outline: 0 and outline-none. Each one needs a reason or a replacement.
- Check the ring over photos, gradients and video by eye. The guarantee covers solid colours only.
- Turn on forced colours (Windows contrast themes, or the DevTools emulation) and Tab again.
- Shift and Tab backwards through long pages and scroll areas. Nothing focused may hide under a sticky header or banner.
- Zoom to 200% and 400%. The ring should grow with the page and stay unclipped.
Questions
Does a two-tone focus ring meet WCAG?
It is W3C's sufficient technique C40 for 1.4.11 Non-text Contrast (for the focus state), 2.4.7 Focus Visible and 2.4.13 Focus Appearance, provided each band is at least 2px thick, the two colours contrast at least 9:1 with each other, and the ring sits over one solid background. It does nothing for 2.4.11 Focus Not Obscured or for the order focus moves in.
Why not use a thicker single-colour ring instead?
Thickness does not fix contrast. Every single colour has backgrounds it disappears against: a 2px or a 6px ring in #2563eb is still 1:1 on a #2563eb button bar. Two bands that contrast with each other cannot both disappear at once.
Which band should go on the inside?
Either works for contrast. Make the inner band the outline and the outer band the box-shadow, as in the recipe, because the outline is the part that survives forced-colours mode, where the browser repaints it in the user's own colour.
Does it work on photos and gradients?
Not with a guarantee. The maths covers one solid colour at a time. On a photo or gradient each band may cross a colour close to its own. The two bands still contrast 21:1 with each other, which usually keeps the ring visible, but check those places by eye.
More on focus
- 2.4.7 Focus VisibleThe AA rule every focusable element has to meet.
- 1.4.11 Non-text ContrastWhere the 3:1 for focus indicators comes from.
- 2.4.13 Focus AppearanceThe AAA size and change-of-contrast test.
- 2.4.11 Focus Not ObscuredSticky headers, banners and what covers focus.
- Focus management guideMoving focus in dialogs, routes and widgets.
- Contrast checkerCheck any ring colour against any background.
Sources
- W3C: Technique C40, Creating a two-color focus indicator
- W3C: Understanding 1.4.11 Non-text Contrast
- W3C: Understanding 2.4.7 Focus Visible
- W3C: Understanding 2.4.11 Focus Not Obscured (Minimum)
- W3C: Understanding 2.4.13 Focus Appearance
- W3C: CSS Color Adjustment Level 1, properties affected by forced colors mode
- MDN: forced-colors media feature