Appearance
Focus Styling
One global rule for focus indicators. No per-element overrides, no box-shadow hacks, no outline: none without a replacement.
The pattern
Define a focus color token and a single :focus-visible rule:
css
:root {
--color-focus: oklch(0.72 0.17 162 / 0.5);
}
:focus-visible {
outline: solid 3px var(--color-focus);
outline-offset: 2px;
}Every focusable element — inputs, buttons, links, custom elements — gets a visible focus ring for free. No per-element rules needed.
Why outline, not box-shadow
- Outlines don't affect layout (no reflow on focus)
- Outlines follow
border-radiusin modern browsers - Outlines work with
outline-offsetto sit outside or inside the element box-shadowis consumed byoverflow: hidden— outlines are not (in most cases)
Why :focus-visible, not :focus
:focus-visible only activates for keyboard navigation, not mouse clicks. Users who click a button don't need a focus ring — users who Tab to it do.
Overflow containers
When a focusable element lives inside a container with overflow: auto or overflow: hidden, an outward outline can be clipped. Fix with an inset outline on those specific elements:
css
.scrollable-list button:focus-visible {
outline-offset: -3px;
}This overrides the global outline-offset: 2px only where clipping is a problem. The outline renders inside the element bounds.
Removing old focus styles
When adopting this pattern, remove:
outline: noneon inputs and buttons (the global rule replaces it)- Per-element
:focus-visiblerules that setbox-shadow border-colorchanges on focus (the outline handles the visual indicator)- Transition properties for
box-shadowthat only existed for focus effects