All articles
Design & UXOctober 17, 2026 6 min read

Focus Rings Are the Accessibility Win You're Still Botching

Focus rings are the cheapest accessibility win in your codebase, and most teams still remove them for aesthetic reasons. Here's how to design and ship them properly in 2026.

Focus Rings Are the Accessibility Win You're Still Botching

Focus rings are the single cheapest accessibility fix in your codebase, and roughly nine out of ten sites we audit still get them wrong. Either they're invisible, or they're the browser default blue halo slapped on top of a carefully designed button, or — worst of all — someone wrote outline: none in a reset file in 2017 and nobody has touched it since.

This is a short, opinionated guide to getting focus states right in 2026, without fighting your designers and without pretending WCAG 2.2 doesn't exist.

Why this still matters

Keyboard users, switch-device users, and anyone navigating a form on a laptop without a trackpad all rely on visible focus. So does the sighted developer tabbing through your checkout to debug it. If they can't tell what's focused, your UI is broken — not degraded, broken.

WCAG 2.2 made this concrete with the Focus Appearance success criterion (2.4.11, AA level). The rough shape of it: the focus indicator must have a minimum area, be at least 2 CSS pixels thick around the component, and have a 3:1 contrast ratio against the unfocused state. Those are not aspirational numbers. They are the floor.

And yet.

/* The crime scene we keep walking into */
*:focus {
  outline: none;
}

If you ship this, you have made your product unusable for a non-trivial slice of your audience. Full stop.

The :focus vs :focus-visible distinction

The reason designers hate focus rings is almost always because they've only ever seen them triggered by mouse clicks. A user clicks a button, the ring flashes on, it looks noisy, the designer files a ticket, the developer nukes the outline.

That ticket is solving the wrong problem. The fix is :focus-visible.

:focus-visible is the browser's heuristic for "this element is focused and the user probably needs to see it." Keyboard tabbing: yes. Clicking a button with a mouse: no. Clicking an input with a mouse: yes, because you need to see where you're typing.

/* Default: no ring on mouse, ring on keyboard */
button:focus {
  outline: none;
}

button:focus-visible {
  outline: 2px solid var(--color-focus);
  outline-offset: 2px;
}

Support has been solid across evergreen browsers for years. There is no reason to be writing :focus rules in new code unless you have a very specific reason to override the heuristic.

When to override the heuristic anyway

Sometimes you do want a visible ring on mouse focus. Text inputs, textareas, contenteditable regions — anywhere the user needs to know their keystrokes will land. For those, keep :focus styling, or use both:

input:focus {
  outline: 2px solid var(--color-focus);
  outline-offset: 0;
  border-color: var(--color-focus);
}

Designing a ring that doesn't look like garbage

The design objection is usually aesthetic. Fair. Here is the kit of parts that makes focus rings look intentional instead of accidental.

Offset. outline-offset is the single most underused property in CSS. A 2–4px offset lets the ring breathe around the component instead of hugging the border. On rounded buttons, this looks dramatically more polished.

Thickness. 2px is the WCAG floor. We tend to use 2px for dense UI and 3px for primary CTAs and marketing surfaces. Anything under 2px fails the spec and also looks weak on high-DPI displays.

Color. The ring needs 3:1 contrast against the adjacent surface, not against the page background. A blue ring on a blue button on a white page can fail even though it "looks fine." Test against the button fill, not the body.

Shape. Use border-radius: inherit on pseudo-element rings, or rely on outline which now respects border-radius in all modern browsers. The era of square rings around pill buttons is over.

Double rings. For dark buttons on dark backgrounds, a single ring often fails contrast on one side or the other. A two-tone ring — inner light, outer dark, or vice versa — solves this and looks genuinely nice.

.btn:focus-visible {
  outline: 2px solid var(--color-bg);
  outline-offset: 0;
  box-shadow: 0 0 0 4px var(--color-focus);
}

The outline sits flush, the box-shadow surrounds it, and you get a crisp inner gap that reads clearly on any background.

Tokenising it in your design system

Focus should be a token, not a one-off decision per component. In our design systems we usually ship three:

  • --focus-ring-width (typically 2px or 3px)
  • --focus-ring-offset (typically 2px)
  • --focus-ring-color (a brand accent that passes 3:1 on every surface)

If you can't find one colour that works on every surface, ship two: --focus-ring-color-on-light and --focus-ring-color-on-dark, and swap them via a theme attribute.

In Tailwind, this maps cleanly to the ring utilities. We tend to extend the config rather than littering focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-brand-500 across every component:

// tailwind.config.js
theme: {
  extend: {
    ringWidth: { DEFAULT: '2px' },
    ringOffsetWidth: { DEFAULT: '2px' },
    ringColor: { DEFAULT: 'var(--color-focus)' },
  },
},

Then components just use focus-visible:ring focus-visible:ring-offset-2 and inherit the tokens. If branding changes the accent next quarter, you edit one CSS variable.

Dealing with overflow: hidden

The classic trap: your card has overflow: hidden to clip an image, you put a focus ring on an inner element, and the ring gets clipped. outline escapes overflow in most cases; box-shadow does not.

Options, in order of preference:

  1. Move the focus target outward so the ring lives on the clipping container.
  2. Use outline instead of box-shadow.
  3. Replace overflow: hidden with a clipped child using clip-path or mask-image.

If you catch yourself writing overflow: visible on a focused state to let the ring escape, stop — you're going to cause layout jank.

Custom components: the part everyone forgets

Native <button>, <a>, and <input> get focus states for free. Your <div role="button"> does not. Neither does your custom combobox, your drag handle, or your canvas-based chart with keyboard navigation bolted on.

A few rules we hold the line on:

  • Any element with tabindex="0" needs an explicit visible focus state. No exceptions.
  • Composite widgets (menus, listboxes, tabs) need focus states on the active descendant, not just the container.
  • If you're using aria-activedescendant, you're responsible for drawing the focus indicator yourself — the browser won't do it.
  • Skip links need a focus state too, and it needs to be visible against your header.

Test this by unplugging your mouse for an afternoon. Not metaphorically. Literally. You will find at least three broken paths before lunch.

Common mistakes we still see in audits

  • Removing outline on :focus but forgetting :focus-visible. Keyboard users get nothing.
  • Using box-shadow for focus without a solid outer edge. Shadows blur, and WCAG wants a defined indicator.
  • Relying on colour change alone. A button that goes from blue to slightly-darker-blue on focus does not pass. You need a shape change — a ring, a border, an underline.
  • Focus rings that disappear during animation. If your button scales on :active, make sure the ring scales with it or is drawn on a parent.
  • Dropdown menus that trap focus inside a closed menu. Open DevTools, tab around, watch the focus vanish into nothing.

Where we'd start

If you inherited a codebase and this article made you nervous, do this on Monday morning:

  1. Grep for outline: none and outline: 0. For each hit, either delete it or pair it with a :focus-visible rule.
  2. Add three focus tokens to your design system and wire them into your button, link, and input components first. Those are 80% of the surface.
  3. Tab through your three most important flows — signup, checkout, main dashboard — with no mouse. Fix what you can't see.
  4. Add an automated check. axe-core and Lighthouse both flag missing focus indicators; wire one into CI so this doesn't regress.

Focus rings are not a design problem pretending to be an accessibility problem. They are an accessibility requirement that benefits from good design. Treat them that way and you'll ship a product that more people can actually use — which, last we checked, was the whole point.

If you'd like a hand auditing your design system for this kind of thing, that's part of what we do on our design and engineering engagements.

#accessibility#css#design-systems#tailwind#ux

Want a team like ours?

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

Start a project