Skip to main content
Syllabus
On this page

Modern CSS Selectors: :is(), :where(), :has()

Badar KhalilUpdated September 29, 2026 5 min read

Why Modern CSS Selectors Matter

For years, CSS developers wished for a CSS parent selector and a shorter way to write long selector lists. Today :is(), :where() and :has() deliver exactly that. These modern CSS selectors reduce repetition, lower specificity problems and remove the need for JavaScript in many UI states. Our focus keyword for this lesson is modern CSS selectors, and you will also learn trending topics such as the CSS :has() parent selector, CSS :is() vs :where(), forgiving selector lists, CSS nesting with :is(), and container-friendly component styling.

The :is() Pseudo-class

:is() takes a list of selectors and matches any element that matches at least one of them. It shortens repetitive code.

Code
/* Before */
header a:hover, nav a:hover, footer a:hover { color: tomato; }

/* After */
:is(header, nav, footer) a:hover { color: tomato; }

Key facts about :is()

  • Its selector list is forgiving: if one selector inside is invalid or unsupported, the others still work. In older syntax, one invalid selector would invalidate the entire rule.
  • The specificity of :is() equals the specificity of its most specific argument. So :is(#id, .class) counts as an ID selector, even when the matched element used the class.
  • It cannot target pseudo-elements such as ::before inside the list.

The :where() Pseudo-class

:where() works exactly like :is() with one massive difference: its specificity is always zero. That makes it perfect for base styles, resets, and library defaults that authors should be able to override easily.

Code
:where(ul, ol) { padding-inline-start: 1.5rem; }

/* Any simple selector overrides it */
.menu { padding-inline-start: 0; }

:is() vs :where() at a glance

:is():where()
Matches selector listYesYes
Forgiving listYesYes
SpecificityHighest argumentAlways 0
Best forShortening component rulesResets, defaults, low-priority styles

The :has() Relational Pseudo-class (Parent Selector)

:has() selects an element based on what it contains or what follows it. It is called the CSS parent selector because for the first time you can style a parent depending on its children.

Code
/* Card that contains an image */
.card:has(img) { display: grid; grid-template-columns: 120px 1fr; }

/* Label of an invalid input */
label:has(input:invalid) { color: crimson; }

/* Heading immediately followed by a paragraph */
h2:has(+ p) { margin-block-end: .25rem; }

Powerful :has() patterns

  • Form validation styling without JavaScript: .field:has(input:user-invalid).
  • Theme toggles: html:has(#dark-toggle:checked) can switch color variables for the whole page.
  • Conditional layouts: change a card layout if it contains a figure, a video or a button.
  • Quantity queries: ul:has(li:nth-child(5)) styles a list only when it has at least five items.
  • Sibling lookahead: .step:has(+ .step.active) styles the step before the active one.

Rules and limitations

  • :has() cannot be nested inside another :has().
  • Pseudo-elements are not valid inside :has().
  • Its specificity equals its most specific argument, just like :is().
  • The list is not forgiving in the same way as :is(). An invalid selector inside can invalidate the rule in some browsers, so test carefully.
  • Browser support: :has() became supported in all major evergreen browsers (Chrome, Safari, Edge, Firefox) by the end of 2023, so it is safe for most modern projects. Use @supports selector(:has(*)) if you need a fallback.

Bonus: :not() for Exclusion

:not() completes the toolkit. Modern browsers accept a selector list inside it: a:not(.button, .nav-link) { text-decoration: underline; }. Like :is(), its specificity is that of its most specific argument.

Combining with CSS Nesting

Native CSS nesting works well with these selectors. Notice how the nested rule uses & together with :is() and :has():

Code
.card {
  padding: 1rem;
  &:has(img) { display: grid; }
  :is(h2, h3) { margin: 0; }
}

Performance Notes

Browsers have optimized :has() heavily, but be sensible. Avoid extremely broad anchors like *:has(.x) or body:has(...) chains with deep descendant checks in huge DOMs. Prefer scoping to a component class, for example .card:has(img).

Best Practices

  • Use :where() for library defaults so that users can override without specificity wars.
  • Use :is() to group parents and reduce repetition, but remember the specificity bump.
  • Scope :has() to a component class for clarity and speed.
  • Provide fallbacks with @supports selector(:has(*)) when supporting older browsers.
  • Keep selectors readable. If a rule needs three nested functional pseudo-classes, refactor with a class.

Summary

:is() shortens selector lists, :where() does the same with zero specificity, and :has() unlocks the long-awaited parent selector. Together they let you write shorter, more powerful, and more maintainable CSS. Experiment with the live examples below.

Try it Yourself HTML
Output

Press Run to execute.

Try it Yourself HTML
Output

Press Run to execute.

Try it Yourself HTML
Output

Press Run to execute.

Try it Yourself HTML
Output

Press Run to execute.

Try it Yourself HTML
Output

Press Run to execute.

Exercise: Refactor with :is()HTML

The CSS below repeats the same declaration for three selectors. Rewrite it using a single :is() selector so the result is identical. Do not change the HTML.

Try it Yourself HTML
Output

Press Run to execute.

Show expected output
All h2 and h3 elements inside section, article and aside are teal, using one rule.

This is a self-check — compare your result with the expected output above.

Exercise: Style a Parent with :has()HTML

Using :has(), give every .post that contains a <video> element a dark background (#111) and white text. Posts without a video must stay unchanged.

Try it Yourself HTML
Output

Press Run to execute.

Show expected output
Only the second post has a dark background and white text.

This is a self-check — compare your result with the expected output above.

Quiz: Modern Selectors Quiz Pass 70%
Loading quiz…

Was this page helpful?