CSS Snippet
CSS :has() Selector
The parent selector we waited 20 years for. Style a parent based on its children — with no JavaScript.
Last updated: October 2026
Quick answer: :has() matches an element if it contains a descendant that matches a selector. For example, form:has(input:invalid) styles the form when any input is invalid. It replaces a lot of JavaScript validation, styling, and state management code.
🎬 Interactive Live Demo
Interact with the form, check/uncheck the checkbox, and toggle the tabs. The parent styles update automatically using :has() — no JavaScript.
💡 Every state change above is driven purely by :has() in CSS. No JavaScript logic for styling.
The Basic Syntax
/* Match any element that contains an img */
.card:has(img) {
/* ... */
}
/* Match a form that contains an invalid input */
form:has(input:invalid) {
border-color: red;
}
/* Match an article with an h2 immediately followed by a p */
article:has(h2 + p) {
/* ... */
}
The pattern is: parent:has(descendant-selector). If the parent contains a matching descendant, the rule applies to the parent.
Form Validation Without JavaScript
The classic use case — style the form based on input state:
/* Red border when any input is invalid (and has been typed in) */
.field:has(input:invalid:not(:placeholder-shown)) {
border-color: #ef4444;
}
/* Green border when all inputs are valid */
.field:has(input:valid):not(:has(input:invalid)) {
border-color: #10b981;
}
/* Highlight the field when its input is focused */
.field:has(input:focus) {
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.15);
}
This is exactly what our live demo does. In most cases, you can replace an entire validation script with three lines of CSS.
Conditional Card Layouts
Style cards differently based on their content:
/* Cards with an image get a top-padding of 0 (for edge-to-edge image) */
.card:has(img) {
padding-top: 0;
}
/* Cards without an image get a centered layout */
.card:not(:has(img)) {
display: flex;
align-items: center;
justify-content: center;
min-height: 200px;
}
/* Cards with a "new" badge get a highlighted border */
.card:has(.badge-new) {
border: 2px solid #10b981;
}
Navigation and Layout Magic
Style parent containers based on active states:
/* Highlight the whole nav section if any link is active */
.nav-section:has(.nav-link.active) {
background: #f59e0b;
}
/* Add a sidebar-adjacent margin only if a sidebar exists */
.main:has(.sidebar) {
margin-left: 250px;
}
/* Style the whole layout when the user is logged in */
body:has(.user-avatar) .header {
background: #1e40af;
}
Combining with :not() and Other Selectors
/* Items that have a subtitle but no description */
.item:has(h2):not(:has(p)) {
/* ... */
}
/* Grid with more than 4 items gets a different layout */
.grid:has(> :nth-child(5)) {
grid-template-columns: repeat(4, 1fr);
}
/* Match any element that has a hovered child */
.menu-item:has(:hover) {
background: rgba(255, 255, 255, 0.1);
}
The :has(:nth-child(5)) pattern is powerful — it lets you change a layout based on how many children there are, all in CSS.
🎯 :has() Use Cases Cheatsheet
| Goal | Selector |
|---|---|
| Style form with invalid input | form:has(input:invalid) |
| Style field with focused input | .field:has(input:focus) |
| Highlight card with an image | .card:has(img) |
| Style parent with active child | .menu:has(.active) |
| Layout with many items | .grid:has(> :nth-child(10)) |
| Exclude items with a specific child | .item:not(:has(.badge)) |
⚠️ Common Mistakes
- Using
:has()for simple descendant matching. If you just need to style the child, use a regular descendant selector.:has()is for styling the parent. - Assuming
:has()is slow. Modern browser implementations are optimized. The performance concerns from early discussions no longer apply. - Chaining
:has()too deeply. Deeply nested:has()is hard to read. Simplify or use a JavaScript class toggle instead. - Forgetting that
:has()doesn't work in::beforeor::after. Pseudo-elements can't contain :has(). Use a real element. - Using it for logic that should be in JavaScript. Use :has() for styling that depends on DOM state. Use JS for business logic.
❓ Frequently Asked Questions
What does the CSS :has() selector do?
The :has() selector matches an element if it contains a specific descendant or matches a specific condition. It is the long-awaited 'parent selector' that lets you style a parent based on its children.
Is :has() supported in all browsers?
Yes. As of 2024, :has() is supported in all modern browsers: Chrome, Safari, Firefox, and Edge. It has been supported in Safari since 2022 and in Firefox and Chrome since 2023.
Can I use :has() to style a form based on input state?
Yes. form:has(input:invalid) styles the form when an input is invalid. form:has(input:focus) styles it when any input is focused. This replaces a lot of JavaScript validation code.
What is the difference between :has() and :not()?
The :not() selector excludes elements that match a condition. The :has() selector includes elements that contain a descendant matching a condition. They serve opposite purposes and can be combined.
Does :has() work with nested elements?
Yes. :has() supports complex selectors inside it, including combinators. For example, article:has(h2 + p) matches articles where an h2 is immediately followed by a p.