Understanding the CSS :where() Function

The CSS where() function is a relatively new feature that has been introduced to provide a more conditional way of grouping selectors, offering a way to specify multiple selectors without specificity impact. Whilst it's a very powerful tool, it's fair to say that its use in some of the projects I've worked on has sparked heated debate about its readability and usability compared to more conventional nesting.
What Is the :where() Function?
In essence, the where() function allows us to group selector alternatives whilst contributing zero specificity for the function and its arguments. A comma‑separated selector list such as div, p, h1 { ... } does not accumulate specificity: each complex selector is calculated independently. By contrast, :is() takes the specificity of its most specific argument. As I've talked about before, specificity in CSS is probably one of the most misunderstood aspects of the language. :where() contributes zero specificity for itself and its arguments. This makes the grouped part easier to override and can help avoid specificity wars in complex stylesheets.
Here's a brief example of how :where() can be used to simplify grouped selectors whilst keeping specificity in check.
Without :where()
.header h1,.header h2,.header p { margin-bottom: 1rem;}With :where()
.header :where(h1, h2, p) { margin-bottom: 1rem;}In both of these examples, the styles apply to the h1, h2, and p elements inside .header. However:
- Each selector in
.header h1, .header h2, .header pis calculated separately; each has specificity (0,1,1). .header :is(h1, h2, p)also has (0,1,1), because:is()adopts the most specific argument..header :where(h1, h2, p)has (0,1,0), because:where()and its arguments contribute zero.
This grouping of selectors inside of :where() can look appealing at first glance, but it also comes with its own problems, making it a little more difficult to read and understand during that same first glance.
Criticisms and Complexity
Despite its benefits, some developers (and especially the ones I've worked with recently) argue that :where() complicates code readability:
Abstract Selectors:
It can obscure which elements the rule targets, especially in larger codebases.Reduced Clarity:
Conventional nesting conveys structural relationships clearly, whilstwhere()only groups selectors, which might not align with intuitive DOM hierarchies.
This second point can be particularly problematic when coming from a BEM background, where naming and. nesting logically follows the structure of the component.
For example, both of these add a 1em margin to the bottom edge of text inputs and buttons within a form:
/* Conventional selectors */form input[type='text'],form button { margin: 1rem;}/* Using where() */form :where(input[type='text'], button) { margin: 1rem;}At a glance, it isn't immediately clear how the where() selectors relate to each other in the DOM, and when they appear mid‑selector like above, they can become lost within the code.
Wrapping Up
The where() function in CSS introduces a powerful way to manage specificity and write modular styles, alongside a novel way to write grouped, nested selectors.
Whilst it's clearly very useful for situations that require zero specificity, its use in abstracting selectors can make code harder to follow compared to conventional nesting. As with any tool, its effectiveness depends on the context and how it is being used within your project.
Key Takeaways
- The
where()function enables conditional grouping of selectors with zero specificity. - It's great for fallback styles and utility classes but may reduce code readability.
- Conventional nesting conveys structural relationships more intuitively.
- Use
where()thoughtfully to balance flexibility with maintainability.
Fin.