CSS Selector: is where has

By Xah Lee. Date: .

the selectors :is(), :where(), :has() are new, supported by all major browsers since 2023.

:is(s1, s2, etc)

matches any compound selector s1, s2 etc.

meaning is basically the same as just s1, s2, etc

:where(s1,s2, etc)

same as the :is but contributes no specificity.

e:has(s1,s2, etc)

match e if it contains descendant s1 or s2 etc.

xtodo

ai answer

CSS Pseudo-Classes Tutorial: :is(), :where(), and :has()

These three powerful pseudo-classes (introduced in recent years) make CSS more expressive, concise, and maintainable. They are part of the Selectors Level 4 specification.

1. :is() – The "Matches Any" Selector

:is() (formerly :matches()) lets you group selectors. It matches an element if any of the selectors inside it match.

Syntax

:is(selector1, selector2, selector3) { ... }

Example 1: Simplifying Headings

/* Before */
h1, h2, h3, h4, h5, h6 {
 color: red;
}

/* After. much cleaner */
:is(h1, h2, h3, h4, h5, h6) {
 color: red;
}

Example 2: Complex combinations

/* Style links that are either in nav or are buttons */
:is(nav a, button.link, .card a) {
  color: royalblue;
  text-decoration: underline;
}

Specificity

:is() takes the highest specificity of the selectors inside it.

:is(#header, .nav) a { }   /* specificity = 0,1,0 (from #header) */

2. :where() – The "Zero-Specificity" Selector

:where() works exactly like :is(), but it always contributes zero specificity (0,0,0).

This is incredibly useful for writing non-intrusive base styles that are easy to override.

Example: Base link styles

/* These won't block more specific rules */
:where(a) {
  color: #0066cc;
}

/* This will override the :where() rule easily */
.nav a {
  color: #ff6600;   /* wins without !important */
}

Real-world use case: Component libraries

/* Library styles */
:where(.button) {
  padding: 0.75rem 1.5rem;
  border-radius: 9999px;
}

/* User can easily override */
.my-custom-button {
  padding: 1rem 2rem;   /* overrides without fighting specificity */
}

Key Difference Summary:

Feature :is() :where()
Specificity Highest inside list Always 0,0,0
Use case Grouping + power Non-intrusive base styles
Override difficulty Higher Very easy

3. :has() – The Relational / "Parent" Selector

:has() is the long-awaited parent selector. It selects an element if it contains (has) a matching descendant or child.

Syntax

parent:has(child) { ... }

Example 1: Style cards that contain images

.card:has(img) {
  border: 2px solid #4ade80;
  padding: 1rem;
}

Example 2: Highlight sections with headings

section:has(h2) {
  background: #f8fafc;
  padding: 2rem;
  border-left: 5px solid #3b82f6;
}

Example 3: Advanced – Adjacent siblings

/* Style a form only if it has an invalid field */
form:has(input:invalid) {
  border: 2px solid #ef4444;
  background: #fef2f2;
}

/* Style a list item only if it contains a checked checkbox */
li:has(input[type="checkbox"]:checked) {
  text-decoration: line-through;
  color: #666;
}

Example 4: :has() with :not()

/* Articles without images */
article:not(:has(img)) {
  max-width: 60ch;
}

Practical Combined Examples

/* 1. Style navigation items that contain a badge */
nav li:has(.badge) {
  position: relative;
}

/* 2. Zero-specificity headings in articles */
article :where(h1, h2, h3, h4) {
  scroll-margin-top: 5rem;
}

/* 3. Complex card variations */
.card:is(:has(img), :has(video)) {
  display: grid;
  grid-template-columns: 1fr 2fr;
}

/* 4. Dark mode cards that contain light content */
.card:has(.light-content) {
  background: #1e2937;
  color: white;
}

Tips & Best Practices

  1. Performance: :has() can be slower than other selectors because it looks at descendants. Use it wisely (avoid on very broad selectors like *).
  2. Specificity: Prefer :where() for utility/base layers.
  3. Chaining: You can combine them:
    article:has(:is(h2, h3)) :where(p) { ... }
  4. Negation: :has() works great with :not():
    .card:not(:has(.price)) { ... }

Interactive Demo Idea

Try this HTML + CSS combo:

<div class="card">
  <h3>Card Title</h3>
  <img src="..." alt="">
</div>

<div class="card">
  <h3>No Image Card</h3>
</div>
.card {
  padding: 1rem;
  border: 1px solid #ddd;
  margin: 1rem 0;
}

.card:has(img) {
  background: #ecfdf5;
}

.card:where(:has(h3)) {
  border-color: #3b82f6;
}

CSS, Selectors