The Power of Modern CSS Selectors: :has, :is, and :where
For years, CSS developers have dreamed of a "parent selector"—a way to style an element based on its children. We also struggled with long, repetitive selector lists that bloated our CSS files.
Modern CSS has finally delivered. With the arrival of :has(), :is(), and :where(), the way we write selectors has fundamentally changed.
1. :has(): The Game-Changing "Parent Selector"
The :has() pseudo-class is arguably the most powerful addition to CSS in a decade. It allows you to select an element if it contains a specific child or if it's followed by a specific sibling.
The "Parent" Use Case
/* Style the card ONLY if it contains an image */
.card:has(img) {
display: flex;
flex-direction: column;
}
/* Style the card ONLY if it has a featured tag */
.card:has(.featured-badge) {
border: 2px solid gold;
}
The "Conditional" Use Case
You can even use it to style labels based on the state of their inputs:
/* Style the label if the checkbox inside is checked */
.form-group:has(input:checked) label {
color: green;
font-weight: bold;
}
2. :is(): Simplifying Complex Selectors
Have you ever written code like this?
header h1, header h2, header h3, footer h1, footer h2, footer h3 {
color: blue;
}
The :is() pseudo-class allows you to group selectors and reduce repetition:
:is(header, footer) :is(h1, h2, h3) {
color: blue;
}
Key Feature: Forgiveness
Unlike traditional selector lists, :is() is forgiving. If one selector in the list is invalid, the browser will ignore the invalid one and still apply the styles to the valid ones.
3. :where(): Zero Specificity Power
The :where() selector works exactly like :is(), but with one crucial difference: it has zero specificity.
Why Specificity Matters
In CSS, the most specific selector wins. This often leads to "specificity wars" where developers use !important or long selector chains to override styles.
/* This has zero specificity impact */
:where(.content) p {
color: gray;
}
/* A simple class selector will now easily override it */
.special-p {
color: red; /* Wins because :where() didn't add any 'weight' */
}
:where() is perfect for CSS Resets and library authors who want to provide default styles that are easy for users to override.
4. Advanced :nth-child with of S
You probably know :nth-child(even), but did you know you can now filter the selection using the of keyword?
/* Select the 2nd item, but ONLY among those with the .visible class */
li:nth-child(2 of .visible) {
background: yellow;
}
This is incredibly useful for dynamic lists where some items might be hidden via display: none.
5. Specificity Calculator: Understanding the Math
CSS specificity is calculated using three columns: (A, B, C).
- A: IDs
- B: Classes, attributes, and pseudo-classes
- C: Elements and pseudo-elements
How the new selectors affect this:
:is()and:has(): Their specificity is equal to the most specific selector in their argument list.:where(): Always has a specificity of (0, 0, 0).
FAQ: Frequently Asked Questions
Q: Is :has() supported in all browsers?
A: Yes! As of late 2023, :has() is supported in the latest versions of Chrome, Safari, Firefox, and Edge. It is safe to use in modern web projects.
Q: When should I use :is() vs :where()?
A: Use :is() when you want the grouped selectors to maintain their normal specificity. Use :where() when you want to make the styles very easy to override (common in base styles or resets).
Q: Does :has() impact performance?
A: Browsers have optimized :has() significantly. While using it hundreds of times on a single page might have a small impact, for most UI patterns, it is extremely fast and efficient.
Write Cleaner, More Declarative CSS
By mastering these selectors, you can significantly reduce the amount of "utility" classes in your HTML and keep your logic where it belongs: in your CSS.
Want to see how your selectors rank in weight? Try our CSS Selector Specificity Calculator (coming soon) to master the math of CSS.