All articles
Design & UXMay 15, 2026 6 min read

Focus Rings in 2026: Stop Hiding Them, Start Designing Them

Most teams either kill focus rings for aesthetics or ship the browser default and call it accessible. Here's how to design rings that survive design review and pass WCAG 2.2.

Focus Rings in 2026: Stop Hiding Them, Start Designing Them

Focus rings are the most ignored part of most design systems, right up until an accessibility audit lands on someone's desk. Then there's a frantic week of patching outline: none out of fifty components, and the result usually looks worse than if nobody had touched it. There's a better way, and in 2026 — with WCAG 2.2 now firmly the baseline most procurement teams check for — it's worth treating focus as a first-class design problem.

Why this got harder in 2026

Two things changed the conversation. First, WCAG 2.2 introduced 2.4.11 Focus Not Obscured and tightened 2.4.13 Focus Appearance, which means the focus indicator can't be hidden behind sticky headers, cookie banners, or chat widgets, and it has to meet specific size and contrast rules. Second, design tools and component libraries finally caught up — Figma variables, Tailwind's focus-visible: variants, and shadcn-style primitives all assume you have a token for focus. If you don't, you're improvising in every component.

The old reflex of *:focus { outline: none; } followed by a half-hearted box-shadow is not enough anymore. Auditors will fail you, and rightly so.

What the spec actually requires

People quote WCAG without reading it, so a quick translation:

  • The focus indicator must have a contrast ratio of at least 3:1 against the adjacent (unfocused) state of the component.
  • The indicator area must be at least as large as a 2 CSS pixel thick perimeter of the focused element, or equivalent.
  • It must not be entirely hidden by author-created content (sticky nav, modals, toasts).
  • :focus-visible is fine — you don't have to show rings on mouse clicks, only keyboard or programmatic focus.

That last point is the unlock. Most designers hate focus rings because they associate them with the ugly blue halo that pops up when you click a button. :focus-visible makes that go away for pointer users, and suddenly the conversation with design becomes much easier.

Designing the ring, not just specifying it

A focus ring is a component. Treat it like one. In our work on design systems we usually define four things at the token level:

  1. Ring color — often two tokens: one for light surfaces, one for dark.
  2. Ring width — typically 2px or 3px. Below 2px you'll fail Focus Appearance on thin elements.
  3. Ring offset — the gap between the element and the ring. 2px is a good default.
  4. Ring style — solid for most cases, sometimes a double ring for elements on busy backgrounds.

Here's the token shape we tend to land on, expressed as CSS custom properties so it maps cleanly to both Figma variables and Tailwind config:

:root {
  --focus-ring-color: #2563eb;
  --focus-ring-color-on-dark: #93c5fd;
  --focus-ring-width: 2px;
  --focus-ring-offset: 2px;
  --focus-ring-offset-color: var(--surface-bg);
}

[data-theme='dark'] {
  --focus-ring-color: var(--focus-ring-color-on-dark);
}

And the utility class or mixin:

.focus-ring {
  outline: var(--focus-ring-width) solid var(--focus-ring-color);
  outline-offset: var(--focus-ring-offset);
  border-radius: inherit;
}

:where(button, a, [role='button'], input, select, textarea):focus-visible {
  @apply focus-ring;
}

A couple of things worth noting. We use outline rather than box-shadow because outline-offset doesn't trigger layout, doesn't get clipped by overflow: hidden, and renders consistently across browsers now. Box-shadow rings used to be the workaround for Safari's old square-cornered outlines, but modern Safari respects border-radius on outlines. The shadow trick is a legacy habit at this point.

Two-tone rings for contrast safety

If your product has wildly varying backgrounds — a dashboard that sits on cards, modals, and image previews — a single color will fail contrast somewhere. The fix is a two-tone ring: an inner ring in your brand color and an outer ring in the surface color, or vice versa.

.focus-ring-two-tone {
  outline: 2px solid var(--focus-ring-color);
  outline-offset: 2px;
  box-shadow: 0 0 0 4px var(--surface-bg);
}

The box-shadow here acts as a separator between the element and the outline, guaranteeing the ring stands out against whatever sits behind it. It's the same trick browsers use for native focus rings on macOS.

The Figma side of the workflow

This is where most teams fall down. Designers mock components in isolation on a clean canvas and never draw the focus state. Engineers then invent one. Six months later a contractor adds a third variant, and the audit finds inconsistency across forty screens.

What works:

  • Add a Focused variant to every interactive component in Figma. Yes, every one. Buttons, inputs, checkboxes, tabs, menu items, cards-as-links.
  • Bind the ring to a Figma variable named the same as your CSS token (focus/ring/color, focus/ring/width). When the token changes, every component updates.
  • Run a contrast plugin (Stark, Able, or the built-in Figma contrast checker) against the focused state on each background color in your system.

If you're already doing the Figma → Tailwind sync we wrote about in our design tokens piece, focus tokens slot into the same pipeline. They're just another set of variables.

The edge cases that always bite

A few patterns that quietly break focus indication in production:

Sticky headers and bottom bars

WCAG 2.2's "focus not obscured" rule is specifically aimed at this. A user tabs down a long form, the focused field scrolls behind your sticky CTA bar, and the keyboard user has no idea where they are. Fix it with scroll-padding:

html {
  scroll-padding-top: 80px;
  scroll-padding-bottom: 96px;
}

Match the values to your sticky element heights. This makes the browser's automatic scroll-into-focus behaviour leave room for your overlay.

Custom controls built from divs

If you're shipping a custom select, combobox, or tree, the focus ring belongs on whichever element actually receives focus — usually the listbox option, not its wrapper. Tab through your own component with the dev tools "show focused element" overlay on. You'll find at least one place where focus lands on an invisible parent.

Overflow clipping

We mentioned this above but it's worth flagging again: box-shadow rings get clipped by overflow: hidden parents. Cards with rounded corners and inner content are the usual culprits. outline doesn't have this problem.

Dark mode and color schemes

A blue ring that reads beautifully on white can disappear on a navy dashboard. Always define a separate focus color per theme, and check it on the busiest background you actually ship — usually a data table row hover state or a chart card.

A quick audit you can run this week

Before investing in a full token rework, do this:

  1. Open your app, hit Tab from the URL bar, and walk through ten user journeys without touching the mouse.
  2. Screenshot every focused state. Drop them into a Figma file side by side.
  3. Flag any that are invisible, partially obscured, lower than 3:1 contrast, or visually inconsistent with siblings.
  4. Count how many distinct ring styles exist. If it's more than two (light surface, dark surface), you have a consolidation problem.

Most teams find six to fifteen variants the first time they do this. That's the real cost of not designing focus rings up front — every component author guesses, and the guesses don't match.

Where we'd start

If you've got an hour: add :focus-visible rules globally, set sensible defaults via CSS variables, and remove every outline: none you can find. That alone moves most products from "failing" to "passing" on 2.4.7.

If you've got a sprint: tokenise focus in Figma and code, add a Focused variant to your top twenty components, and write a Storybook or Ladle story that renders every interactive component in its focused state on each surface. Make that page part of your visual regression suite. Focus rings stop drifting the moment you can see them all in one place.

It's a small surface area with outsized impact — keyboard users, screen magnifier users, and anyone navigating one-handed on a phone with a Bluetooth keyboard all notice immediately. Designing it properly costs less than apologising for it later.

#Accessibility#Design Systems#CSS#UX

Want a team like ours?

72Technologies builds production software for the kind of teams who actually read this blog.

Start a project