Ch. 5 · HTML & CSS

The CSS :has() Selector

Style a parent based on its children with :has(), handle form states, and use it without hurting performance.

~2 min readadvancedupdated Oct 5, 2026

For years CSS could not style a parent based on its children, which pushed state styling into JavaScript or extra classes. The :has() selector changes that: it matches an element that contains a matching descendant, so a card can style itself when it holds an image.

Before you start

You should be comfortable with selectors and combinators. This article covers :has() and its uses.

Step-by-step walkthrough

Step 1: Match a parent by its contents

.card:has(img) matches a card that contains an image, so the card can change its layout when an image is present. This replaces adding a modifier class from JavaScript or a template, keeping the styling declarative.

Step 2: Style form state

:has() can react to form controls: .field:has(input:invalid) styles a field when its input is invalid, and .form:has(:checked) reacts to a selection. This covers validation and selection states in CSS, which previously needed script.

Step 3: Keep it reasonably scoped

: has()relies on the browser matching descendants, so a very broad selector across a huge tree can be costly. Scope it to a specific class rather than the whole document, and prefer:has(> child)` where a direct child is intended, which is both clearer and cheaper.

Worked scenario

The field styles itself when its input is invalid.

.field:has(input:invalid) {
  border-color: crimson;
}
.card:has(> img) {
  grid-template-rows: auto 1fr;
}
CSS

Walk through the example

The field turns crimson when the input inside it is invalid, giving immediate visual feedback without script. The card changes its grid when it has a direct image child, which handles the with-image and without-image layouts from the markup itself. Both read as “style this element if it has that”.

Common mistake

Using :has() with a very broad or deeply nested selector across the whole document, which can be slow, or relying on it without a fallback for older browsers that do not support it.

Verify the behavior

Toggle an input’s validity and confirm the parent field styles. Add and remove an image and confirm the card layout changes. Compare rendering with a direct-child selector versus a descendant selector for scope.

Interview exercise

Why was :has() a significant addition to CSS?

Answer and reasoning

Because it enables selecting a parent based on its descendants, which previously required JavaScript or a class added by the template. Many stateful patterns such as form validation feedback and layout-by-content can now be expressed purely in CSS, which keeps the markup and behavior declarative and reduces script.

Continue learning

Compare selectors in Focus visible and cascade in Cascade layers. Read the MDN :has() documentation and try the HTML and CSS interview questions.

More in HTML & CSS

read ✓HTML & CSS · easy

CSS aspect-ratio and Layout Stability

Reserve space for media with aspect-ratio, combine it with object-fit, and avoid the layout shift the old padding hack caused.

~2 min readread →
read ✓HTML & CSS · hard

CSS Cascade Layers

Control the cascade order with @layer, keep specificity low, and understand how unlayered styles fit in.

~2 min readread →
read ✓HTML & CSS · mid

CSS Dark Mode with prefers-color-scheme

Support light and dark themes with prefers-color-scheme and custom properties, and avoid the flash of the wrong theme.

~2 min readread →
esc