ESC

Type to search the knowledge base.

outline vs border for Focus

Keep focus visible with outline or box-shadow — why border shifts layout, :focus-visible patterns, and never outline: none alone.

beginner3 min read
  • css
  • outline
  • focus
  • a11y

Keyboard users navigate by focus. Removing focus styles with outline: none and no replacement is an accessibility defect (WCAG 2.4.7 Focus Visible). outline and box-shadow are the usual tools because they don’t steal layout space the way border does.

Docs: MDN outline, :focus-visible, WCAG focus visible.

outline vs border

outline border
Affects layout box size No (drawn outside/over) Yes — can shift neighbors
Follows border-radius Historically uneven; modern engines improve Yes
Offset control outline-offset N/A
Can be non-rectangular Browser-dependent Box edges
.button:focus-visible {
  outline: 2px solid var(--brand);
  outline-offset: 2px;
}
/* Border focus — layout jank risk */
.button {
  border: 2px solid transparent;
}
.button:focus-visible {
  border-color: var(--brand); /* OK if transparent border reserved */
}

If you insist on border for focus, always reserve the same border width in the resting state (transparent) so size doesn’t change.

The box-shadow focus ring

Design systems often prefer shadow rings for radius fidelity:

.button {
  border-radius: 0.5rem;
  transition: box-shadow 120ms ease;
}

.button:focus {
  outline: none; /* only after providing an alternative */
}

.button:focus-visible {
  outline: none;
  box-shadow:
    0 0 0 2px var(--surface),
    0 0 0 4px var(--brand);
}

Double ring (surface + brand) keeps contrast on both light and brand-colored buttons.

:focus vs :focus-visible

/* Mouse click may show focus ring with :focus only — product choice */
.input:focus {
  border-color: var(--brand);
}

/* Keyboard-first ring */
.input:focus-visible {
  outline: 2px solid var(--brand);
  outline-offset: 1px;
}

:focus-visible matches when the user agent decides focus should be highlighted (typically keyboard). Use it so mouse users aren’t stuck with persistent rings on every click, without punishing keyboard users.

Never do this

/* Forbidden pattern */
*:focus {
  outline: none;
}

If you reset outline globally, you must define :focus-visible styles for interactive elements (links, buttons, inputs, custom widgets with tabindex).

High contrast / forced colors

@media (forced-colors: active) {
  .button:focus-visible {
    outline: 2px solid Highlight;
    outline-offset: 2px;
    box-shadow: none;
  }
}

System colors may override your brand ring — ensure something still shows.

Interview out-loud

“I don’t remove focus outlines without a replacement. Outline and box-shadow communicate focus without reflow; border can shift layout unless width is reserved. I style :focus-visible for keyboard-visible rings and verify contrast. Global outline: none is a red flag in code review.”

Footguns

  1. outline: none with no alternative.
  2. Low-contrast rings on brand buttons.
  3. Rings clipped by overflow: hidden parents.
  4. Focusing a wrapper but styling only an inner child.
  5. Custom components with tabindex="0" but no focus CSS.

Component checklist

  • Every a[href], button, input, select, textarea, and [tabindex="0"] has a visible :focus-visible style.
  • Contrast of the ring against adjacent colors meets non-text contrast expectations where applicable.
  • Rings are not clipped by overflow: hidden without an inner focus treatment.
  • Dark and light themes both verified.
  • High contrast / forced-colors mode still shows a system ring.
.card:focus-within {
  box-shadow: 0 0 0 2px var(--surface), 0 0 0 4px var(--brand);
}

:focus-within is useful when the focusable control sits inside a larger chrome piece (custom select button face).

Further reading

Related guides