DEV Community

RAXXO Studios
RAXXO Studios

Posted on Originally published at raxxo.shop

Focus States That Survive a Redesign: A Keyboard-First Checklist

  • Redesigns silently break focus states; I run a 6-point check before every ship

  • Replace :focus with :focus-visible so mouse clicks stay quiet, keyboards stay loud

  • A 3:1 contrast outline against both themes is the bar I hold

  • Tab the whole page once with the mouse unplugged, no exceptions

A visual refresh almost always eats the keyboard experience. The design looks cleaner, the colors are nicer, and somewhere in the diff the focus outline quietly vanished. I've shipped enough redesigns to treat this as a certainty, not a risk, so I built a short checklist I run every single time.

Here's the whole thing. You can run it on your own site in about twenty minutes.

Why Redesigns Eat Focus States First

Focus is invisible until you need it. That's the core problem. When I'm staring at a new layout in the browser, I'm clicking with a mouse, and a mouse never shows me a focus ring. So the one thing a keyboard user depends on is the one thing I'm least likely to notice is gone.

The usual culprit is a CSS reset. A designer drops in outline: none to kill the default browser ring because it "looks ugly," and then forgets to add anything back. Now every button, link and input is a dead end for anyone using Tab.

The second culprit is a component library swap. You move from one set of styled components to another, and the new ones ship with their own focus behavior. Maybe it's there, maybe it's a 1px gray line that disappears against a gray background. You inherited someone else's decision and never checked it.

The third is theme work. You add a dark mode, the outline color was hardcoded to a dark value, and now it's invisible on the dark canvas. The light theme is fine. The dark one is a black box, literally.

I treat the redesign diff as guilty until proven innocent. Any time I touch global styles, swap a component, or add a theme, I assume focus is broken and I go check. This is the same defensive habit behind The Undo Button Every RAXXO Tool Ships With: assume the scary path is the one nobody tests, then test it on purpose.

One more thing worth saying plainly. Keyboard users aren't a tiny edge group. Power users navigate with Tab because it's faster. Screen reader users depend on it. People with motor conditions that make a mouse painful rely on it entirely. When the focus ring dies, all of them lose their place on the page at once.

Swap :focus For :focus-visible

The single highest-value change I make is replacing :focus with :focus-visible.

Old focus styling had an annoying tradeoff. If you styled :focus to be loud enough for keyboard users, mouse users saw a big ring every time they clicked a button, which looked wrong. So people removed it, and both groups lost out.

:focus-visible solves this. The browser decides. Click a button with a mouse, no ring. Tab to it with a keyboard, loud ring. Same CSS, different behavior based on how the element got focus.

My baseline rule looks like this:


:focus-visible {
  outline: 2px solid currentColor;
  outline-offset: 2px;
}
:focus:not(:focus-visible) {
  outline: none;
}

Enter fullscreen mode Exit fullscreen mode

The outline-offset matters more than people expect. A ring pressed flat against the element edge reads as a border, not a focus state. Two pixels of gap makes it obvious the element is selected, and it keeps the ring from clipping on rounded corners.

I use outline, not box-shadow, for the ring. An outline doesn't trigger layout shift and it doesn't get clipped by overflow: hidden on a parent. box-shadow focus rings vanish inside scroll containers all the time, and you won't catch it until something scrolls.

There's one trap with outline: none as a reset. Never, ever apply it globally without a :focus-visible style to replace it. If you write *:focus { outline: none } and nothing else, you've deleted keyboard navigation for the entire site in one line.

I've seen this ship in production more than once.

Test the swap by unplugging your mouse, or just setting it aside, and pressing Tab. Every interactive element should light up as you move. If one doesn't, it's either not actually focusable (a div pretending to be a button) or your ring style doesn't apply to it.

This is the kind of small, boring default I bake into the starting point so I never have to think about it again, same idea as The Starter Template Every RAXXO Tool Begins From. Set it once, inherit it everywhere.

Contrast The Ring Against Both Themes

A focus ring that exists but can't be seen is the same as no ring. The number I hold it to is a 3:1 contrast ratio between the outline color and whatever's directly behind it.

Three to one is the threshold for non-text UI elements in the accessibility guidelines. A focus indicator counts. So I check the ring against the element's own background and against the page background, because the offset gap means the ring often sits half on one and half on the other.

The theme problem is where most of the failures hide. I run the check against every background the element can appear on. Light theme, dark theme, and any colored sections like a hero band or a footer with an inverted palette. One outline color almost never passes against all of them.

My fix is to tie the ring color to the theme, not hardcode it:


:root { --focus-ring: #1a1a1a; }
[data-theme="dark"] { --focus-ring: #f5f5f5; }

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

Enter fullscreen mode Exit fullscreen mode

For elements that sit on unpredictable backgrounds, like a button over a photo or a video, I add a second ring with a contrasting color so at least one half is always visible:


:focus-visible {
  outline: 2px solid #fff;
  outline-offset: 2px;
  box-shadow: 0 0 0 4px #000;
}

Enter fullscreen mode Exit fullscreen mode

White ring, black halo. One of them wins no matter what's behind it. I'd only reach for the box-shadow here, where I actually need the double layer, and I'd accept the clipping risk because the fallback matters more.

Check the ratio with any contrast tool. Pick the outline color, pick the background, read the number. If it's under 3:1, bump the outline to a thicker 2px or change the color. Don't make the ring thinner to look subtle. Subtle and invisible are the same thing to the person who needs it.

While you're in there, check that focus works in forced-colors mode too. Windows high contrast mode overrides your colors entirely, and outline survives that override where box-shadow sometimes doesn't. That's the other reason I default to outline.

Tab The Whole Page, Mouse Unplugged

This is the test that catches everything the CSS checks miss. I put the mouse away and Tab through the entire page, start to finish, like I have no other option.

The order should follow the visual layout. Top to bottom, left to right, the way you'd read. If focus jumps from the header to the footer and back up to the middle, your DOM order doesn't match your visual order, and a keyboard user is being bounced around the page with no map.

This breaks constantly after a redesign because CSS lets you move things visually without touching the DOM. You use flexbox order or grid placement to put a sidebar on the left that lives at the bottom of the HTML. Looks right, tabs wrong. Fix the DOM order, not the tab order. Resist tabindex with positive numbers; it's a patch that makes the next person's job harder.

Things I watch for on the walk:

  • A visible focus ring on every single stop, no silent jumps.

  • Nothing focusable that's hidden. If a mobile menu is closed, its links shouldn't be in the tab order.

  • No trap. I can Tab out of every modal, dropdown and date picker. If I open something and Tab cycles forever inside with no escape, that's a trap, and Escape should close it.

  • A skip link as the first stop, so keyboard users can jump past the nav to the main content instead of tabbing through forty menu items on every page.

The modal trap is the one that bites after a component swap. A good modal traps focus on purpose while open, then returns focus to the button that opened it when it closes. A bad one traps focus forever, or dumps you back at the top of the page where you've lost your place entirely. Open every overlay, Tab around, hit Escape, confirm focus landed somewhere sensible.

I also tab through after resizing to a narrow width, because mobile layouts reorder things and hide others. The desktop tab order can be perfect while the mobile one is a mess.

Do this walk once per major view. It takes minutes and it's the closest thing to actually being a keyboard user for five minutes. If you only run one item on this list, run this one. The naming and structure discipline from The Command Naming Convention Every RAXXO Tool Follows is the same spirit: predictable order so nobody gets lost.

Bottom Line

Run the four checks in order and you'll catch almost every focus regression a redesign introduces. Swap in :focus-visible, give the ring 2px of offset, hold it to 3:1 contrast against every background including dark mode, then tab the whole page with the mouse put away.

If I had to keep just one, it's the tab walk. The CSS can look perfect in the stylesheet and still fail in the actual flow, and the only way to know is to use the page the way a keyboard user does.

Add this to your pre-ship routine and it becomes muscle memory. I keep mine in the same place I keep the rest of my launch gates, because a check you have to remember is a check you'll skip on the busy day.

If you want the full picture of how I set defaults like this once and inherit them across everything I build, the Claude Blueprint walks through the whole approach. Go unplug your mouse and tab your homepage. You'll find at least one dead stop.

Top comments (0)

Some comments may only be visible to logged-in visitors. Sign in to view all comments.