The :is() and :where() CSS Pseudo-classes

August 17, 2024 | 3 minutes
TL;DR: The :is() and :where() pseudo-classes work the same way, but :is() has higher specificity than :where().

With the :is() and :where() pseudo-classes, it's possible to apply styles for multiple selectors with less duplicative code. While it's not necessary to use these pseudo-classes, they can help you write shorter selectors and make your styles easier to read. These pseudo-classes accept a list of selectors as an argument and select any element that can be chosen from that list for styling. This selector, for example, would apply styles to any paragraph or list item element within a div: div :is(p, li).

Differences between the Traditional Selector Approach and the :is(), :where() Pseudo-Classes

The traditional approach to applying a set of styles to multiple elements looks like this:

1div h2,
2div p,
3div li {
4    border: 1px solid red;
5}

While there's nothing wrong with this approach, it means extra typing, longer selectors, and more chances to mistype part of the selector. Using the :is() and :where() pseudo-classes, the same styles can be written like this:

1div :is(h2, p, li) {
2    border: 1px solid red;
3}
4
5div :where(h2, p, li) {
6    border: 1px solid red;
7}

The two style declarations above work the same way except they have different specificity. In CSS, specificity determines the order in which styles are applied to DOM elements, creating the cascade part of Cascading Style Sheets (CSS).

The traditional and :is() approaches have the same specificity, so if both are used in the same UI, then the one that's specified last will take precedence. In the example below, the :is() approach is the one that would be applied to the elements; moving the :is() approach above the traditional approach would apply the traditional approach code to the elements instead.

1div h2,
2div p,
3div li {
4    border: 1px solid red;
5}
6
7div :is(h2, p, li) {
8    border: 1px solid blue;
9}

The :where() pseudo-class is less specific than the traditional and :is() approaches. It has a specificity of 0 and will only be applied if more specific selectors aren't used in the UI. If the traditional and/or :is() approaches are used in the same UI, the :where() styles will never be applied regardless of the ordering of the style declarations.

It can be helpful to use :where() for browser reset styles or other baseline styles that you want to make it easy for other people to override. Using :where() might make sense, for example, when setting baseline styles in components when you expect developers to apply a theme and override those styles.

1div h2,
2div p,
3div li {
4    border: 1px solid red;
5}
6
7div :is(h2, p, li) {
8    border: 1px solid blue;
9}
10
11/* ❌ Will never be applied even though it's last. Needs
12 * to be used without conflicting style declarations in
13 * order to be applied. */
14div :where(h2, p, li) {
15    border: 1px solid green;
16}

Here's a CodePen where you can experiment with these pseudo-classes.

Invalid Selectors

If an invalid selector is passed as an argument to :is() or :where(), it will be ignored and the valid selectors will still be considered. In this selector, for example, :madeup isn't a real selector, but styles would still be applied to p if it's found in the DOM: :where(:madeup, p).

Using :is() and :where() to select pseudo-elements

You cannot use :is() or :where() to select pseudo-elements like ::before.

1// ❌ Won't work
2:is(p::before) {
3    content: '2. ';
4}
5
6// ❌ Won't work
7:where(p::before) {
8    content: '3. ';
9}
10
11// ✅ Will work
12p::before {
13    content: "1. ";
14}

Browser Support

The :is() and :where() pseudo-classes are currently well supported in all major browsers.