Selectors Intermediate

Selecting parent elements without JavaScript

Selecting a parent based on its children used to require JavaScript. :has() handles it in pure CSS, no event listeners needed.

Old JS required
1// Watch for changes, find parent
2document.querySelectorAll('input')
3  .forEach(input => {
4    input.addEventListener('invalid', () => {
5      input.closest('.form-group')
6        .classList.add('has-error');
7    });
8  });
Modern
3 lines
1.form-group:has(input:invalid) {
2  border-color: red;
3  background: #fff0f0;
4}

5/* Pure CSS. Zero JavaScript. */
JS is adding/removing .has-error class on the parent
🚫

No JavaScript needed

Eliminates an entire class of DOM-manipulation code. Fewer event listeners, fewer bugs.

Instant response

Browser applies styles in the rendering pipeline, no waiting for JS execution or reflow.

🧩

Composes naturally

Chain with other selectors: .nav:has(.dropdown:hover), body:has(dialog[open]).

Browser Support
91%
Chrome Firefox Safari Edge
Lines Saved
8 → 3
JS → pure CSS
Old Approach
JavaScript
Event listeners + DOM
Modern Approach
Pure CSS
Zero runtime cost

How it works

:has() is a relational pseudo-class. When you write .card:has(img), it selects any .card that contains an img as a descendant. It's the parent selector that CSS lacked for 25 years.

You can use any selector inside :has(): pseudo-classes like :invalid, :checked, :hover, or even combinators like :has(> .direct-child). It unlocks conditional styling that previously required JavaScript.

ESC