Focus styles that respect every input method

The focus indicator—the ring or highlight that marks the currently focused element—is the keyboard user's stand-in for a mouse pointer. Losing it means losing your place on the page. Browser defaults may not fit your design, but when you restyle focus, the goal is always the same: make sure users can still tell where they are.

:focus for an unconditional indicator

The :focus pseudo-class applies to any element in focus, no matter the input device. You can make a <div> focusable with a tabindex attribute and style it directly:

div[tabindex="0"]:focus {
  outline: 4px dashed orange;
}

With :focus, the element looks the same when focused by mouse, keyboard, or stylus. The catch is that browsers are inconsistent about when an element receives focus at all. A <button> styled with a custom :focus rule will show that style when clicked in Chrome on macOS, but not in Safari on macOS, where clicking the button does not put it in focus.

button:focus {
  outline: 4px dashed orange;
}

That means you need to test your focus styles across browsers and input methods. If focus behavior differs, your carefully-crafted indicator may never appear for some users.

:focus-visible when the indicator actually helps

The :focus-visible pseudo-class matches only when the browser's heuristics decide a focus indicator is genuinely useful. Typically, that is when the most recent interaction came from the keyboard and the key press did not include a meta, ALT, OPTION, or CONTROL key.

button:focus-visible {
  outline: 4px dashed orange;
}

With :focus-visible, a control can show a ring for keyboard navigation while staying clean for mouse clicks.

:focus-within for highlighting a whole region

The :focus-within pseudo-class applies to an element when it, or any child element, receives focus. This makes it easy to draw attention to a region, such as a form, when interaction moves inside it:

form:focus-within {
  background: #ffecb3;
}

With this rule, the form gets its highlight both when the form itself is selected and when any of its radio buttons are selected.

Deciding when to show a ring

A quick heuristic helps settle whether a control should always show a focus indicator or only under certain conditions: imagine clicking the control on a mobile device. Would a keyboard appear?

  • If yes, always show a focus indicator regardless of input device. Text inputs like <input type="text"> need this because the user must type into them no matter how focus arrived.
  • If no, a selective indicator is fine. Clicking a <button> with a mouse or a touchscreen completes the action, so the ring adds little. For keyboard users, however, the ring is essential—it tells them whether ENTER or SPACE will activate the control.

Why outline: none is a dead end

Browser rules for when the default ring appears are genuinely confusing. Merely giving an element a tabindex or restyling a <button> can change the default behavior.

Removing the indicator outright is tempting but short-sighted for users who need it:

/* Don't do this!!! */
:focus {
  outline: none;
}

A more robust approach combines :focus with the :focus-visible polyfill. The polyfill lets you apply the selective behavior consistently, even where native support is missing:

/*
  This hides the focus indicator if the element receives focus with a
  mouse, but it still shows up on keyboard focus.
*/
.js-focus-visible :focus:not(.focus-visible) {
  outline: none;
}

/*
  Optionally: Define a strong focus indicator for keyboard focus.
  If you choose to skip this step, then the browser's default focus
  indicator will be displayed instead.
*/
.js-focus-visible .focus-visible {
  ...
}

Used this way, you get a clear indicator for keyboard users and avoid a permanent ring that annoys mouse users.