CSSworkingbaseline-2023

The :has() selector

CSS gets a parent selector.

For two decades CSS could only descend. :has() finally lets a parent style itself based on what's inside it β€” or anywhere downstream.

Style a parent based on its children

.card:has(img) β€” a card that contains an image. form:has(:invalid) β€” a form with at least one invalid input. .row:has(input:checked) β€” the parent row of a checked checkbox.

It's not just direct children

:has() looks at any descendant by default. Combine with > for direct children only: .card:has(> img).

The "previous sibling" trick

CSS still has no :previous-sibling selector β€” but .a:has(+ .b) selects every .a that has a .b immediately after it.

Asking "are we there yet?"

The two examples above react to invalid state. The inverse is just as useful: :has(:valid):not(:has(:invalid)) lights up only when every required field is satisfied β€” a positive "ready to submit" affordance. And because :has() accepts structural pseudo-classes, you can count siblings (:has(> :nth-child(6))) to switch layout once a collection crosses a quantity threshold β€” the long-wished-for "quantity query".

Try it 4 examples

Highlight the row containing a checked box

HTML+CSS+JSintro
Press Run to execute

Only the "Slice ginger" row renders highlighted β€” amber border, amber text, struck through β€” because its box is checked and li:has(input:checked) reaches back up from the input to style the whole li. Toggle any other box and that row lights up too, proving the parent reacts to a descendant's state with no JavaScript.