Modern CSS (2025–2026)Extra· 35 min read

The :has() Parent Selector

For decades CSS could only style children based on parents — never the other way round. The :has() selector finally lets a parent react to what is inside it.

What you will learn

  • Explain what :has() does in plain words
  • Style a parent based on its children
  • React to sibling and form states with :has()

The problem :has() solves

Until recently, CSS selectors only flowed downwards: you could style a paragraph inside a card, but you could never style the card based on what was inside it. Designers called this “the parent selector that did not exist”. The new :has() selector fixes exactly that — it lets you select an element because of what it contains.

Read :has() as the word “if it has”. The selector .card:has(img) means “a .card that has an <img> inside it”. The element that actually gets styled is the .card (the parent) — not the image.

Note: Plain-English definition: :has() is a relational pseudo-class. “Relational” just means it looks at an element’s relationship to other elements (its children or siblings) and styles it based on what it finds.

Style a parent based on its child

Here is the idea in action. We have two cards: one contains an image, one does not. With a single rule, the card that has an image gets a coloured border — without adding any class to it. Read the code, then the line-by-line walk-through below:

A parent styled by its child with :has()
<style>
  .card { border: 2px solid #e6e8f0; border-radius: 12px; padding: 16px; margin-bottom: 12px; }
  /* Style the CARD itself, only when it contains an <img> */
  .card:has(img) { border-color: #4338ca; background: #eef2ff; }
  .card img { width: 100%; border-radius: 8px; margin-bottom: 8px; }
</style>

<div class="card">
  <img src="https://picsum.photos/300/120" alt="photo">
  <p>This card HAS an image — notice my indigo border.</p>
</div>
<div class="card">
  <p>This card has no image — plain grey border.</p>
</div>
Live preview

Walk through what each part does:

  • .card — the base style: a plain grey border, applied to both cards.
  • .card:has(img) — the new rule. It reads “select any .card that has an <img> somewhere inside it”. Only the first card matches, so only it turns indigo. The second card has no image, so it stays grey.
  • The element being styled is the .card (the parent). The img in the brackets is only the condition — it is never the thing that changes.

Note: Output: The first card has an indigo border on a light-blue background (because it contains an image); the second card keeps its plain grey border. No class was added to either card — the difference comes entirely from :has().

A worked example — highlight a checked option

A very common real use is a form. We want the whole option row to highlight when its checkbox is ticked. Before :has() this needed JavaScript; now it is one CSS rule. The rule .option:has(input:checked) means “the .option row that has a checked input inside it”:

Tick a box and its whole row highlights
<style>
  .option {
    display: flex; align-items: center; gap: 10px;
    border: 2px solid #e6e8f0; border-radius: 10px; padding: 12px; margin-bottom: 8px;
  }
  /* Highlight the whole row when its checkbox is ticked */
  .option:has(input:checked) { border-color: #16a34a; background: #ecfdf5; }
</style>

<label class="option"><input type="checkbox"> I agree to the terms</label>
<label class="option"><input type="checkbox" checked> Send me updates (pre-ticked)</label>
Live preview

Tick the first checkbox in the preview and watch its entire row turn green. The rule .option:has(input:checked) styles the row (the .option), but only while it has a checked input inside. The input:checked part is the condition; the .option is what changes colour. This “light up the parent when the child is in a state” pattern is the everyday job of :has().

Tip: :has() also reads siblings. For example h2:has(+ p) targets an h2 that is immediately followed by a paragraph. Combined with :not() — like form:has(:invalid) — it lets pure CSS react to states that used to require JavaScript.

Watch out: You cannot nest :has() inside another :has(), and very broad rules (like body:has(.modal) on a huge page) can cost performance. Keep the conditions specific and scoped to where you need them.

Q. What does .card:has(img) select?

Answer: :has() styles the element (the card) based on what it contains. .card:has(img) selects the card that has an image inside it — not the image.

✍️ Practice

  1. Give a <figure> a caption-coloured border only when it :has(figcaption).
  2. Highlight a form field’s wrapper with .field:has(input:focus) so the whole row reacts when you click into the input.

🏠 Homework

  1. On a card grid, use :has() to visually mark every card that contains a “Sold out” badge — no extra classes allowed.
Want to learn this with a mentor?

CodingClave runs guided, project-based training (28-day, 45-day & 6-month batches).

Explore Training →