search

Understanding the CSS :has Property with Interactive Examples

For years, CSS had no way to select a parent based on what it contained. If a div held an image, you couldn’t style that div differently – not without JavaScript. The :has pseudo-class, introduced in CSS Selectors Level 4, changes that completely.

I started using css :has() the day Firefox shipped support in late 2023, and it’s become one of my most-used selectors. It enables patterns that previously required event listeners and DOM queries.

What is the CSS :has Property?

The :has pseudo-class is a relational selector that matches an element when its descendants (or siblings) satisfy a given condition. Think of it as asking “does this element contain X?” directly in CSS.

Syntax

element:has(selector) {
    /* styles */
}

Here, element is the target you want to style, and selector describes what must exist inside it. You can also use combinators: element:has(> selector) restricts the check to direct children only.

Practical Examples

Here are a few common patterns. Each one includes the static code and a live demo you can interact with.

Example 1: Highlighting a Parent Based on a Child

Style a div when it contains an img element:

<div>
    <p>This div contains an image.</p>
    <img src="https://via.placeholder.com/150" alt="Placeholder Image">
</div>
<div>
    <p>This div does not contain an image.</p>
</div>
div:has(img) {
    border: 2px solid green;
    padding: 10px;
}

Try it yourself – click the button below to toggle an image inside the left box and watch the border change:

Interactive Demo

This box can contain an image.

This box never has an image. No green border.

CSS rule: .ch-box:has(img) { border-color: #2d8a4e; }

Example 2: Styling a List’s Parent Based on a Child Class

Target a ul when it contains a li with a specific class:

<ul>
    <li>Item 1</li>
    <li class="highlight">Item 2</li>
    <li>Item 3</li>
</ul>
<ul>
    <li>Item A</li>
    <li>Item B</li>
    <li>Item C</li>
</ul>
ul:has(li.highlight) {
    background-color: lightyellow;
    padding: 10px;
}

Example 3: Form Validation Styles

Style a form group’s container based on the validity state of its input – no JavaScript required for the styling:

<form>
    <div class="form-group">
        <label for="email">Email:</label>
        <input type="email" id="email" required>
    </div>
    <button type="submit">Submit</button>
</form>
.form-group:has(input:invalid) {
    border: 2px solid red;
    padding: 10px;
}

Type in the input below to see the border react in real time. The CSS :has(input:invalid) and :has(input:valid) selectors handle the visual feedback:

Interactive Demo
CSS rule: .ch-form-group:has(input:invalid) { border-color: #e05555; }
CSS rule: .ch-form-group:has(input:valid) { border-color: #2d8a4e; }

Real-World Use Cases

The basic examples above show how :has() works. But the real value becomes clear in production patterns that used to require JavaScript.

Dark Mode Toggle with :has(:checked)

A single checkbox can control an entire component’s color scheme. The parent container uses :has(#darkmode:checked) to flip between light and dark styles. Zero lines of JavaScript.

.card:has(#darkmode:checked) {
    background: #0e1219;
    color: #f1eeee;
}

.card:has(#darkmode:checked) .btn {
    background: #FFB42F;
    color: #0e1219;
}

Toggle the switch below to see it in action:

Interactive Demo - Pure CSS, Zero JavaScript
Card Component
This card flips between light and dark themes using only CSS :has(:checked). The checkbox state drives the entire color scheme - background, text, and button. No JavaScript involved.
Action Button
.ch-card:has(#ch-darkmode:checked) { background: #0e1219; color: #f1eeee; }

Empty State Detection

Show a placeholder message when a container has no items. Combine :has() with :empty or the negation pseudo-class:

.list-container:not(:has(li)) .empty-state {
    display: block;
}

.list-container:has(li) .empty-state {
    display: none;
}

This removes the need for JavaScript checks like if (list.children.length === 0). The browser handles visibility reactively as items are added or removed.

Quantity Queries

Style a grid differently based on how many items it contains. If there are 4 or more children, switch to a multi-column layout:

.grid:has(:nth-child(4)) {
    grid-template-columns: repeat(2, 1fr);
}

.grid:not(:has(:nth-child(4))) {
    grid-template-columns: 1fr;
}

Before :has(), you’d need JavaScript to count children and toggle a class. Now CSS handles it automatically.

Comparison with JavaScript

Before :has, parent-conditional styling required DOM queries and manual class toggling. Here is the same effect as Example 1 in vanilla JavaScript:

document.addEventListener("DOMContentLoaded", function() {
    const divs = document.querySelectorAll('div');

    divs.forEach(div => {
        if (div.querySelector('img')) {
            div.style.border = '2px solid green';
            div.style.padding = '10px';
        }
    });
});

The CSS version is a single line. But beyond brevity, there is a fundamental difference: the CSS rule is reactive. If an image is added or removed later, the style updates instantly. The JavaScript version only runs once on page load.

AspectCSS :has()JavaScript
Lines of code1-35-10+
Reactive to DOM changesYes, automaticNeeds MutationObserver
PerformanceBrowser-optimizedScript execution + reflow
Works without JSYesNo
Complex logic (API calls, conditions)NoYes

The :has pseudo-class is fast in modern browsers, but unconstrained selectors like :has(img) without a type prefix force the engine to check every element on the page. Prefer constrained selectors like div:has(img) and use the direct child combinator div:has(> img) when you only care about immediate children.

Browser Support

The :has pseudo-class reached Baseline status in December 2023 and is now supported by all major browsers. Chrome 105+, Firefox 121+, Safari 15.4+, and Edge 105+ all handle it, covering over 96% of global users as of early 2026.

You can use CSS @supports with @supports selector(:has(*)) for feature detection if you still need a fallback path. In practice, the remaining unsupported browsers are primarily older Android WebView and Samsung Internet versions.

Data on support for the css-has feature across the major browsers from caniuse.com

FAQs

Common questions about the CSS :has() pseudo-class:

What is the CSS :has() pseudo-class?
A relational pseudo-class from CSS Selectors Level 4 that lets you style a parent element based on its descendants. For example, div:has(img) targets any div that contains an img.
Is CSS :has() supported in all major browsers?
Yes. Chrome 105+, Firefox 121+, Safari 15.4+, and Edge 105+ support it, covering over 96% of users globally as of early 2026. It reached Baseline status in December 2023.
Can :has() replace JavaScript for parent selection?
For styling, yes. :has() handles parent-child styling natively in CSS without DOM queries or event listeners. It is also reactive - styles update automatically when the DOM changes. JavaScript is still needed for logic beyond styling, like API calls or complex conditions.
Does using :has() affect page performance?
Unconstrained selectors like :has(img) can slow rendering because the engine checks every element. Prefix with a type or class (div:has(img)) and use the direct child combinator (:has(> img)) to keep things fast.
Can I combine :has() with other pseudo-classes?
:has() composes with :not(), :is(), :where(), and state pseudo-classes like :checked or :invalid. For example, form:has(input:invalid) targets forms containing invalid inputs.
What is the difference between :has() and :is()?
:is() matches an element itself against a list of selectors. :has() matches an element based on what it contains. For example, div:is(.red, .blue) matches divs with those classes, while div:has(.red) matches divs that contain a child with class .red.

Conclusion

The :has pseudo-class gives CSS true parent selectors, something developers waited years for. I reach for it on almost every project now – form validation indicators, dark mode toggles, conditional layouts. Browser support is universal at this point.

If you’re exploring more modern CSS features, check out native CSS nesting, the CSS @property rule, or the mask-image property.

Join the Discussion
0 Comments  ]

Leave a Comment

To add code, use the buttons below. For instance, click the PHP button to insert PHP code within the shortcode. If you notice any typos, please let us know!

Savvy WordPress Development official logo