has

A pseudo-class that selects an element based on the state of its descendants or following siblings. Replaces the JavaScript that toggled classes to "select a parent" with plain CSS.

Modern approach

Style a row depending on whether it contains a checked checkbox. The style updates automatically when the state changes, so no JavaScript is needed.

tr:has(input[type="checkbox"]:checked) {
  background-color: #fef3c7;
}

Legacy approach

With no selector for parent elements, the style was applied to a highlight class (.is-checked). JavaScript listened for checkbox changes and toggled the class on the row. It also had to sync the initial state, in case a checkbox was already checked when the page loaded.

tr.is-checked {
  background-color: #fef3c7;
}
for (const checkbox of document.querySelectorAll('tr input[type="checkbox"]')) {
  const row = checkbox.closest('tr')
  const update = () => row.classList.toggle('is-checked', checkbox.checked)
  update()
  checkbox.addEventListener('change', update)
}

Things to watch when replacing

  • :has() cannot be nested inside another :has().
  • Pseudo-elements (such as ::before) cannot be used inside :has().
  • If any selector inside :has() is invalid, the whole :has() is invalid. A typo or an unsupported selector makes the entire rule stop working.
  • :has() is re-evaluated whenever its descendants change. Anchoring it on a broad element such as body:has(...) widens what has to be checked, so keep it on the closest parent you can.

Fallback

All five target browsers support it, so you normally don’t need to keep the old technique. If you also have to support older browsers, keep both the legacy CSS and the modern CSS as separate rules. Combining them into one selector list makes browsers without :has() ignore the whole rule. Run the JavaScript only in browsers that don’t support :has().

// Add the legacy class only in browsers that don't support :has()
if (!CSS.supports('selector(:has(*))')) {
  const checkboxes = document.querySelectorAll('tr input[type="checkbox"]')
  for (const checkbox of checkboxes) {
    const row = checkbox.closest('tr')
    const update = () => row.classList.toggle('is-checked', checkbox.checked)
    update()
    checkbox.addEventListener('change', update)
  }
}

Browser Support

  • Chrome

    v105+
    2022-09-02
  • Edge

    v105+
    2022-09-01
  • Safari

    v15.4+
    2022-03-14
  • Safari (iOS)

    v15.4+
    2022-03-14
  • Firefox

    v121+
    2023-12-19

Search by feature name or code

e.g. aspect-ratio, :has(), padding-top

Try a different word or a property name.

Could not load search.