StealThis .dev

:has() Parent Interactions

Four self-contained demos where the CSS :has() relational selector styles a parent from its descendants — an invalid/checked form that highlights its own fields, a card grid that dims siblings on hover, a layout that reflows by counting selected items, and a no-JS tabs and accordion driven entirely by :has(:checked).

Open in Lab
css
Targets: JS HTML

Code

This demo is a tour of the CSS :has() relational pseudo-class — the long-awaited “parent selector” — showing four patterns that previously needed JavaScript. A sign-up form styles each field wrapper from the state of the input it contains: .field:has(input:user-invalid) paints a red boundary, .field:has(input:valid:not(:placeholder-shown)) turns it green, and the submit button unlocks only when the whole <form>:has() the required boxes checked. A pricing card grid uses .grid:has(.card:hover) .card:not(:hover) so hovering any card dims and desaturates its siblings, spotlighting the focus.

The third panel is a quantity-driven layout: a plan picker whose container reflows and recolors purely from how many options are selected, using :has() combined with sibling counting so one, two, or three-plus choices each produce a different grid. The last panel is a fully no-JS component set — tabs and an accordion — where hidden radio and checkbox inputs drive visibility through :has(:checked), proving you can build stateful UI without a single event listener.

Everything visual is CSS; the only JavaScript reads the live :has() state back out to update a small readout and to render the exact selector firing at each moment, so you can watch the cascade react. A @supports selector(:has(*)) feature detect swaps in an accessible fallback banner and static styling for browsers that predate :has(), keeping every panel usable while clearly flagging when true native :has() support is active.