Pseudo-classes Hover Focus Focus-visible
Interactive pseudo-classes — :hover, :focus, :focus-visible, :active, :focus-within — with keyboard-safe patterns and touch caveats.
- css
- pseudo-classes
- focus
Pseudo-classes style elements in a state. For interactive UI the core set is :hover, :focus, :focus-visible, :active, and :focus-within. Mixing them up is how you ship sticky mouse rings or invisible keyboard focus.
Docs: MDN pseudo-classes, :focus-visible, :focus-within.
Quick map
| Pseudo-class | When it matches |
|---|---|
:hover |
Pointer over element (and often ancestors) |
:active |
During activation (click/press) |
:focus |
Element has focus |
:focus-visible |
Focus should be visibly indicated (UA heuristic; typically keyboard) |
:focus-within |
Element or descendant has focus |
.button {
background: var(--brand);
transition: background 120ms ease, transform 80ms ease;
}
.button:hover {
background: var(--brand-hover);
}
.button:active {
transform: translateY(1px);
}
.button:focus-visible {
outline: 2px solid var(--brand);
outline-offset: 2px;
}
:focus vs :focus-visible
/* Shows for mouse clicks too in many cases */
input:focus {
border-color: var(--brand);
}
/* Prefer for keyboard rings */
input:focus-visible {
outline: 2px solid var(--brand);
outline-offset: 1px;
}
Product pattern:
- Subtle border/background on
:focusfor inputs (always clear which field is active). - Strong ring on
:focus-visiblefor buttons/links. - Never
outline: nonewithout a visible replacement.
:focus-within for groups
.field {
border: 1px solid var(--border);
border-radius: 0.5rem;
padding: 0.5rem 0.75rem;
}
.field:focus-within {
border-color: var(--brand);
box-shadow: 0 0 0 3px color-mix(in srgb, var(--brand) 25%, transparent);
}
<label class="field">
<span>Email</span>
<input type="email" name="email" />
</label>
Styles the wrapper when the nested control is focused — cleaner than restyling only the raw input.
Hover on touch devices
:hover can stick after tap on some devices or never apply. Essential actions must work without hover. Progressive enhancement:
@media (hover: hover) and (pointer: fine) {
.card:hover {
transform: translateY(-2px);
}
}
Order and LVHA/focus
Classic link order advice (LoVe HAte) still avoids override surprises:
a:link { }
a:visited { }
a:hover { }
a:focus-visible { }
a:active { }
Interview out-loud
“:hover is pointer-over, :active is pressed, :focus means focused, :focus-visible is when a focus indicator should show — usually keyboard. :focus-within styles parents of focused descendants. I don’t rely on hover for essential UI and I never remove outlines without a :focus-visible style.”
Footguns
- Global
*:focus { outline: none }. - Hover-only disclosure menus without keyboard paths.
- Forgetting
:focus-visibleon customtabindex="0"widgets. - Using
:focusrings that fail contrast on brand backgrounds. - Assuming hover equals “mouse user” forever on hybrid devices.
Button state matrix
.button {
background: var(--brand);
color: white;
transition: background 120ms ease, box-shadow 120ms ease, transform 80ms ease;
}
.button:hover { background: var(--brand-hover); }
.button:active { transform: translateY(1px); }
.button:disabled,
.button[aria-disabled="true"] {
opacity: 0.55;
cursor: not-allowed;
transform: none;
}
.button:focus-visible {
outline: 2px solid var(--brand);
outline-offset: 2px;
box-shadow: 0 0 0 4px color-mix(in srgb, var(--brand) 30%, transparent);
}
Disable hover lift on disabled controls. Ensure :disabled styles win over :hover by source order or specificity.
Cards and whole-hit targets
When an entire card is clickable, prefer a real link stretched with CSS (pseudo-element overlay) rather than a div with click handlers. Focus styles must still show on the actual focusable element. :focus-within on the card can highlight the container while the inner link receives focus — a pattern that keeps semantics intact without hover-only affordances.
Related
Further reading
Related guides
- outline vs border for FocusKeep focus visible with outline or box-shadow — why border shifts layout, :focus-visible patterns, and never outline: none alone.
- 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.
- blend-mode Basicsmix-blend-mode vs background-blend-mode — how pixels combine, isolation, and the stacking/readability traps in product UI.