Skip to content
CSS

CSS :has() Selector

The parent selector CSS never had — style parents based on their children and siblings.

stellae.design

7 min read

Introduction

The :has() selector lets you select an element based on what it contains or what follows it — something CSS couldn't do before. It's often called the 'parent selector,' but it's more versatile than that.

Style a form group when its input is invalid, change a card layout when it contains an image, or highlight a list item when its checkbox is checked — no JavaScript needed.

It's one of the most powerful additions to CSS in recent years, enabling patterns that previously required JavaScript class toggling.


Key Concepts

Basic Syntax

css
.card:has(img) { grid-template-rows: 200px 1fr; }
h2:has(+ p) { margin-bottom: 0.5rem; }
.card:not(:has(img)) { padding: 2rem; }

Combining with Other Selectors

css
.field:has(input:invalid) { border-color: red; }
.field:has(input:focus) { box-shadow: 0 0 0 3px rgba(59,130,246,0.3); }
body:has(.modal.open) { overflow: hidden; }

Practical Examples

1. Conditional Card Layouts

css
.card:has(.card__image) {
  display: grid;
  grid-template-columns: 1fr 2fr;
}
.card:not(:has(.card__image)) {
  display: block;
  padding: 1.5rem;
}

2. Form Field States

css
.form-group:has(:required) label::after {
  content: " *";
  color: red;
}

3. Checkbox-Driven UI

css
.todo-item:has(input:checked) .todo-text {
  text-decoration: line-through;
  opacity: 0.6;
}

4. Interactive Navigation

css
nav:has(.dropdown:hover) {
  background: rgba(0,0,0,0.95);
}
.nav-item:has(.dropdown:hover) > a {
  color: var(--color-primary);
}

Best Practices

  • Use :has() to replace JavaScript class toggling for visual state changes.
  • Combine with :not() for powerful conditional styling.
  • Keep selectors readable — deeply nested :has() hurts maintainability.
  • Use for progressive enhancement — default styles serve as fallback.
  • Test performance with many elements.

Common Pitfalls

  1. Performance in large DOMs — complex :has() selectors can be slower.
  2. :has() is forgiving — invalid selectors inside won't break but never match.
  3. Over-using :has() when a simple class or attribute selector is clearer.
  4. Not providing fallback styles for the ~9% without support.

Browser Support

Chrome 105+, Safari 15.4+, Firefox 121+, Edge 105+. ~91% global support.


  • CSS Nesting — combine nesting with :has() for clean conditional styles
  • CSS Pseudo-Elements Masterclass — conditionally show pseudo-elements
  • Event Handling Best Practices — when CSS :has() replaces JS handlers
CSScss

Was this article helpful?