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:
<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>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.cardthat 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). Theimgin 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”:
<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>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?
✍️ Practice
- Give a
<figure>a caption-coloured border only when it:has(figcaption). - Highlight a form field’s wrapper with
.field:has(input:focus)so the whole row reacts when you click into the input.
🏠 Homework
- On a card grid, use
:has()to visually mark every card that contains a “Sold out” badge — no extra classes allowed.