// css · Web Platform Advent #17
The CSS :has() Selector: the Parent Selector, Explained (2026)
The :has() relational pseudo-class lets you style an element based on what it contains or what follows it - the long-awaited parent selector. How it works, real examples, browser support, and the gotchas.
The :has() pseudo-class is the feature CSS authors asked for for two decades: a way to style an element based on what is inside it, or what comes after it. It is usually called the "parent selector," though it is really more powerful than that - it is a relational selector. It now ships in every modern browser, so you can use it in production today.
The basic idea
A normal CSS selector targets an element by its own type, class or state. :has() flips that around: it targets an element because of the elements it contains. You write the parent, then :has(...) with a selector for what must be inside.
/* Style any .card that contains an <img> */
.card:has(img) {
padding: 0;
}
/* Style a <figure> only when it has a caption */
figure:has(figcaption) {
border: 1px solid #ddd;
} The element that actually gets styled is the one before :has() - the .card and the figure, not the image or caption inside. That is the whole trick: the condition is about the descendants, but the target is the ancestor.
It is relational, not just "parent"
Because you can put combinators inside :has(), it does far more than look at children. You can react to siblings, which was impossible in CSS before.
/* A heading that is immediately followed by a paragraph */
h2:has(+ p) {
margin-bottom: 0.25rem;
}
/* A form that contains an invalid field */
form:has(input:invalid) {
border-left: 3px solid crimson;
}
/* A label whose related required input is empty and focused */
.field:has(input:required:placeholder-shown) .hint {
opacity: 1;
} That second example is the kind of thing people used to reach for JavaScript to do: change the look of a whole form (or any container) based on the state of a control inside it. With :has() it is one line of CSS, and it updates live as the user types.

Practical patterns
- Quantity and content queries: style a gallery differently when it has exactly one image versus many -
.gallery:has(img:only-child). - Previous-sibling styling: CSS could only ever look forward with
+and~;:has()lets an earlier element react to a later one, e.g.label:has(+ input:checked). - Layout that adapts to content: give a card a two-column layout only when it actually contains an image, and a single column when it does not.
- Form and validation states: highlight a whole fieldset when any control inside is invalid, without a line of script.
Browser support
Support is broad in 2026. :has() shipped in Chrome and Edge 105 (August 2022) and Safari 15.4, with Firefox joining in version 121 (December 2023). That means it is now available across the current versions of every major browser, and it is safe to rely on for most audiences. For older browsers you can treat :has() styles as a progressive enhancement, since a browser that does not understand the selector simply ignores the rule.
Gotchas worth knowing
- You cannot match pseudo-elements inside :has(). Selectors like
::beforeare not allowed as the argument. - :has() itself is not allowed inside :has(). You cannot nest one relational check inside another.
- Keep the scope reasonable. Very broad selectors like
body:has(...)that force the browser to re-check large parts of the page can cost performance on big, frequently-updating documents. In practice, scoped selectors on components are fine; engines have optimised the common cases. - The target is the subject, not the match. If a rule is not applying, check that you are styling the element before
:has(), not the one inside it.
FAQ
Is :has() really the CSS parent selector? Effectively yes, and more. It selects an ancestor based on its descendants, which is the classic "parent selector" use case, but because it accepts combinators it can also select based on following siblings, so it is better described as a relational selector.
Can I use :has() in production in 2026? For most sites, yes - it is supported in the current versions of all major browsers. Use it as a progressive enhancement if you must support very old browsers.
Does :has() hurt performance? Rarely, if you keep selectors scoped. Extremely broad, document-wide :has() checks on large, constantly changing pages can add cost, but component-level usage is fine.
For more modern CSS, see our guides on native CSS nesting and container queries.