All articles
CSS Fundamentals

CSS :has() — The Parent Selector That Changes Everything

14 October 2024 7 min read CSS Fundamentals

CSS :has() is the selector developers have wanted for 20 years. Learn how to style parents based on their children, create conditional layouts and reduce your JavaScript dependency.

For two decades, CSS had no way to style a parent based on its children. You wanted to highlight a form when it contained an invalid input? JavaScript. :has() changes that.

Basic syntax

:has() matches an element if any of its descendants match the selector inside:

/* Style a form that contains an invalid input */
form:has(input:invalid) {
  border-color: #e07070;
}

/* Style a card that contains an image */
.card:has(img) {
  padding: 0;
}

/* Style a nav that contains the active link */
nav:has(a.active) {
  background: var(--accent-dim);
}
Conditional layouts

One of the most powerful use cases is changing layout based on content:

/* Grid when there are 3+ items, flex for fewer */
.grid:has(:nth-child(3)) {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
}

/* Full-width image card vs text-only card */
.card:has(img) {
  grid-column: span 2;
}

.card:not(:has(img)) {
  background: var(--bg-card);
}
Form state styling without JavaScript
/* Highlight label when its input is focused */
.field:has(input:focus) label {
  color: var(--accent);
}

/* Show error message when field is invalid and dirty */
.field:has(input:invalid:not(:placeholder-shown)) .error-msg {
  display: block;
}

/* Style submit button based on form validity */
form:has(input:invalid) button[type="submit"] {
  opacity: 0.5;
  pointer-events: none;
}
Browser support

:has() has excellent support as of 2024 — Chrome 105+, Safari 15.4+, Firefox 121+. Global browser support is above 90%. It's production-ready.

@supports selector(:has(a)) {
  /* Safe to use :has() here */
  nav:has(.active) { background: var(--accent-dim); }
}