CSS :focus-within Explained
The :focus-within pseudo-class styles a parent element when any descendant has focus — perfect for highlighting a form field's container.
The :focus-within pseudo-class matches an element if it, or any of its descendants, currently has focus. It solves a problem plain :focus can’t: styling a container — a search bar, a form group, a card — based on whether something inside it is focused, without any JavaScript.
The problem it solves
:focus only matches the element that is actually focused. If a user tabs into an <input> nested inside a <div class="search-bar">, :focus can style the input, but it can’t reach up and style the wrapping div. Historically, doing that required a focus event listener that toggled a class on the parent — a small but real chunk of JavaScript for a purely visual effect.
:focus-within moves that logic into CSS:
.search-bar {
border: 1px solid #ccc;
transition: border-color 0.15s ease;
}
.search-bar:focus-within {
border-color: #2563eb;
box-shadow: 0 0 0 2px rgba(37, 99, 235, 0.2);
}
Now the whole search bar lights up when its <input> is focused — no listener, no state, no cleanup.
How it matches
:focus-within matches an element when:
- The element itself is focused, or
- Any element it contains (at any depth) is focused.
That “any depth” part matters. A <form> with a dozen nested <div>s will still match :focus-within when a deeply buried <input> receives focus. This makes it well suited to component wrappers where you don’t control — or don’t want to hardcode — how deep the focusable element sits in the markup.
:focus-within vs :focus vs :focus-visible
These three pseudo-classes look similar but answer different questions:
| Pseudo-class | Matches | Typical use |
|---|---|---|
:focus | The element itself, when focused | Styling the focused control directly |
:focus-visible | The element itself, only when the browser thinks focus should be visibly indicated (e.g. keyboard nav) | Focus rings that don’t appear on mouse clicks |
:focus-within | The element or any descendant that is focused | Highlighting a container around a focused child |
For a deeper look at the difference between the first two, see :focus-visible vs :focus. You can combine all three: a card might use :focus-within to highlight itself while an inner button uses :focus-visible to avoid a focus ring on click.
Practical patterns
Highlighting a fieldset. Group related inputs and show which group is active:
fieldset:focus-within {
background: #f8fafc;
}
Revealing hidden UI on focus. A common accessible pattern is a “skip to content” link or a toolbar that’s visually hidden until something inside it is reachable by keyboard:
.toolbar {
opacity: 0;
pointer-events: none;
}
.toolbar:focus-within {
opacity: 1;
pointer-events: auto;
}
This is more robust than :hover-only reveal patterns, which strand keyboard users entirely — tabbing into the toolbar’s buttons now reliably shows it. This pairs well with the broader guidance in web accessibility basics: don’t rely on hover for anything a keyboard or screen-reader user needs to reach.
Dropdown menus without JavaScript. A parent <li> can reveal a nested <ul> when a link inside it is focused, giving keyboard users the same reveal behavior mouse users get from :hover:
li ul {
display: none;
}
li:hover ul,
li:focus-within ul {
display: block;
}
Selector combinations
:focus-within composes with other selectors the same way any pseudo-class does. It works well with :has() for more conditional styling — for example, styling a form label differently depending on whether its associated input is both focused and invalid. It also combines naturally with structural selectors like :nth-child() when you need to target specific rows in a focused table or list.
Browser support and gotchas
:focus-within has been broadly supported across modern browsers for years and needs no fallback for current evergreen browsers. A couple of things to watch for:
- It does not fire for elements that are focusable only in principle (e.g. a
<div>withouttabindex) — the descendant actually has to receive focus. - Removing an element from the DOM while it’s focused (or its focused descendant) removes the
:focus-withinmatch immediately, since there’s nothing left to hold focus. - It has no effect on elements that can never contain focusable descendants, like a plain
<span>with only text — there’s nothing inside for focus to move into.
The takeaway
:focus-within lets a container react to focus landing anywhere inside it, replacing a common class of JavaScript focus-tracking with a single CSS selector. Reach for :focus when you’re styling the focused element itself, :focus-visible when you want to suppress focus rings on mouse interaction, and :focus-within when a parent needs to respond to focus happening somewhere inside it.
Tagged
Keep reading
Takina · · 4 min read prefers-reduced-motion: Building Accessible CSS Animation
The prefers-reduced-motion media query detects a user's OS-level motion setting so CSS animations can be toned down or removed for people who need it.
Takina · · 4 min read What Is CSS Subgrid? Nested Grid Alignment Explained
CSS subgrid lets a nested grid item inherit its parent's track sizing, so children can align to the same columns or rows across unrelated containers.
Takina · · 4 min read CSS field-sizing Property Explained
The CSS field-sizing property lets form controls like textareas grow to fit their content automatically, without JavaScript resize listeners.