:has()

The Parent Selector

CSS never had a way to select an element by what's inside it, so something like ‘style this card differently if it contains an invalid input’ meant JavaScript. :has() matches an element if any of its descendants match the selector you pass it.

The CSS

Copied
.list {
  border: 1px solid var(--border);

  /* at least one child checkbox is checked */
  &:has(input:checked) {
    border-color: var(--accent);
  }

  /* no child is left unchecked, i.e. everything is done */
  &:not(:has(input:not(:checked))) {
    border-color: var(--success);
  }
}

.item:has(input:checked) {
  opacity: 0.55;

  & span {
    text-decoration: line-through;
  }
}

How it works

Check an item and its row styles itself through .item:has(input:checked), with no handler toggling a class. The container goes further: :has(input:checked) nudges its border toward the accent as soon as one box is ticked, and :not(:has(input:not(:checked))), or “no child is left unchecked”, flips it to green once they all are.

That last rule is the fun one. Composing :not() and :has() gets you “all of these match” even though CSS has no every() selector.

Widely supported

Chrome / Edge105+Safari15.4+Firefox121+
:has() · tic-tac-toe

turns, wins and reset, all CSS

@property · counter()
hover me
cos() · sin()

trigonometry, in CSS

:has() quantity query

switch guests on