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.
- 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
outline: nonewith no alternative.- Low-contrast rings on brand buttons.
- Rings clipped by
overflow: hiddenparents. - Focusing a wrapper but styling only an inner child.
- 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-visiblestyle. - Contrast of the ring against adjacent colors meets non-text contrast expectations where applicable.
- Rings are not clipped by
overflow: hiddenwithout 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).
Related
- Pseudo-classes hover focus focus-visible
- Focus management
- Focus visible styles
- Keyboard accessibility checklist
Further reading
Related guides
- prefers-reduced-motionHonor prefers-reduced-motion — cut decorative animation, keep essential feedback, and wire CSS and JS motion to one preference.
- Pseudo-classes Hover Focus Focus-visibleInteractive pseudo-classes — :hover, :focus, :focus-visible, :active, :focus-within — with keyboard-safe patterns and touch caveats.
- Accent Color and Form ControlsUse accent-color to theme native checkboxes, radios, ranges, and progress — what it paints, what it skips, and when to custom-style instead.
- Auto-fit vs Auto-fillauto-fit collapses empty tracks; auto-fill keeps them. Use both with minmax for responsive card grids without media-query soup.
- BEM Naming MethodologyBlock, Element, Modifier naming for CSS — how it limits specificity wars, what to name, and when utility or CSS modules beat strict BEM.