Syntax
:has(selector)Examples
Styling a Parent Based on Its Children
Highlighting a form group only if it contains an invalid input.
/* Demo setup: sizing, structure or neutral surfaces. */
.form-group{padding:1rem}input{display:block;margin-block:.5rem;padding:.5rem}
.form-group:has(input:invalid) {
border-left: 3px solid red;
background: #fef2f2;
}- The invalid email input makes its form-group parent match :has(input:invalid).
- Enter a valid address such as john@example.com to remove the invalid-parent styling.
- CSS reflects validity; this standalone preview does not submit or register anything.
HTML used by this example
<div class="form-group"><label for="email">Email address</label><input id="email" type="email" value="not-an-email" required></div>Conditional Layout Based on Content
Changing a card's layout depending on whether it contains an image.
/* Demo setup: sizing, structure or neutral surfaces. */
.card{display:grid;gap:1rem;border:1px solid #94a3b8;padding:1rem;margin-block:1rem}.preview-photo{width:100px}
.card:has(img) {
grid-template-columns: 100px 1fr; /* image + text layout */
}
.card:not(:has(img)) {
grid-template-columns: 1fr; /* text-only layout */
}- Both articles use the same base grid display.
- The presence of an image selects the two-column variant.
- The article without an image uses one column; :has does not create the parent’s grid formatting context by itself.
HTML used by this example
<article class="card"><img class="preview-photo" src="data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22480%22%20height%3D%22240%22%20viewBox%3D%220%200%20480%20240%22%3E%3Crect%20width%3D%22480%22%20height%3D%22240%22%20fill%3D%22%23e0f2fe%22%2F%3E%3Ccircle%20cx%3D%22365%22%20cy%3D%2255%22%20r%3D%2230%22%20fill%3D%22%23fbbf24%22%2F%3E%3Cpath%20d%3D%22M0%20240%20135%2055%20280%20240%22%20fill%3D%22%230f766e%22%2F%3E%3Cpath%20d%3D%22m150%20240%20155-150%20175%20150%22%20fill%3D%22%235b21b6%22%2F%3E%3Ctext%20x%3D%2220%22%20y%3D%22218%22%20fill%3D%22white%22%20font-size%3D%2224%22%20font-family%3D%22sans-serif%22%3EOriginal%20landscape%3C%2Ftext%3E%3C%2Fsvg%3E" alt="A sun above green and purple mountains"><p>A lesson with an illustration.</p></article><article class="card"><p>A text-only lesson.</p></article>Sibling-Based Styling
A powerful combination: styling an element based on the state of a sibling.
/* Demo setup: sizing, structure or neutral surfaces. */
.field input{border:2px solid #64748b;padding:.5rem}
.field:has(+ .error-message) input {
border-color: red; /* highlights an input if an error message follows it */
}- The error message is the field wrapper’s adjacent sibling.
- :has(+ .error-message) therefore identifies the preceding field wrapper.
- The nested input receives the error border; a real validation flow must also connect messages accessibly.
HTML used by this example
<div class="field"><label for="topic">Topic</label><input id="topic" value=""></div><p class="error-message">Choose a topic.</p>Parameters and values
- :has(selector) (pseudo-class): Matches if the given selector matches anything inside the element
Best practices
- Use :has() to replace many patterns that previously required JavaScript, like styling a form field based on its own validity state
- Combine :has() with :not() to style elements that specifically lack certain children
- Keep :has() selectors reasonably simple - deeply nested conditions can become hard to reason about, even though they are valid
- Use :has() for genuinely conditional styling based on content structure, not as a general replacement for simple class-based styling
Browser compatibility
- chrome
- Yes
- firefox
- Yes
- safari
- Yes
- edge
- Yes
These are the existing reference’s support labels, not tested minimum versions. Individual values and APIs can have different support. Check the linked specification before choosing a browser target.
CSS specifications (modules)