A Complete Guide to the CSS :is() Pseudo-Class

A Complete Guide to the CSS :is() Pseudo-Class

Modern CSS pseudo-classes are changing selector writing, offering developers unprecedented flexibility and precision in styling web applications.

By allowing developers to group multiple selectors within a single declaration, :is() eliminates verbose, repetitive CSS rules while maintaining clean, readable stylesheets. This powerful selector enables more concise and expressive styling strategies that were previously cumbersome or impossible to implement efficiently.

This article will dive deep into the :is() pseudo-class, exploring its syntax, behaviour, and practical applications. We'll examine how it can streamline your CSS, reduce code complexity, and provide more elegant solutions to common styling challenges. Subsequent articles in this series will explore the complementary :where() and :has() pseudo-classes, completing our comprehensive exploration of modern CSS selector techniques.

Basic Usage

The :is() pseudo-class function accepts a selector list and matches any element that can be selected by one of the selectors in that list. This is useful for writing large selectors in a more compact form.

Instead of writing this:

header p, main p, footer p {
    color: #f00;
}

We can write this concise declaration:

:is(header, main, footer) p {
    color: #f00;
}

The result is the same:

Advantages of using the :is() pseudo-class over multiple selectors

  1. Reduced Repetition and Cleaner Code

    Problem with Multiple Selectors

    When you need to apply the same styles to multiple selectors, you often end up repeating the same rules:

     .header .nav li:hover,
     .main .nav li:hover,
     .footer .nav li:hover {
       color: #00ff88;
       background-color: #333;
       padding: 0.5rem;
     }
    

    This approach is verbose and difficult to maintain. You must edit each selector individually if you need to update the styles.

    Solution with :is()

    The :is() pseudo-class allows you to group selectors, reducing repetition:

     :is(.header, .main, .footer) .nav li:hover {
       color: #00ff88;
       background-color: #333;
       padding: 0.5rem;
     }
    

    Benefits:

    • Cleaner code: No repeated rules.

    • Easier maintenance: Update styles in one place.

    • Improved readability: The intent of the selector is clearer.

  1. Forgiving Selector Lists

    Problem with Multiple Selectors

    If one selector in a comma-separated list is invalid, the entire rule is ignored:

     header p,
     main p,
     ::footer p { /* This breaks the entire rule */
       color: #0000ff;
     }
    

    As illustrated below, none of the paragraphs is coloured blue because the last rule uses an invalid selector ::footer.

    Solution with :is()

    The :is() pseudo-class is forgiving: if one selector in the list is invalid, the others still work:

     :is(header, main, ::footer) p {
       color: #f00;
     }
    

    In the above code snippet, only the last paragraph is not rendered with red text because it uses an invalid selector ::footer.

    Benefits:

    • Robustness: Invalid selectors don’t break the entire rule.

    • Safer development: Easier to experiment with selectors without breaking existing styles.

  1. Improved Specificity Control

    Problem with Multiple Selectors

    When using multiple selectors, specificity can become unpredictable or unnecessarily high:

     #header .nav li:hover, /* Specificity: (1,1,1) */
     .main .nav li:hover,   /* Specificity: (0,1,1) */
     .footer .nav li:hover { /* Specificity: (0,1,1) */
       color: #00ff88;
     }
    

    Solution with :is()

    The :is() pseudo-class uses the highest specificity in its selector list, making it easier to manage:

     :is(#header, .main, .footer) .nav li:hover {
       color: #00ff88;
     }
    

    Here, the specificity is (1,1,1) because #header is the most specific selector in the list.

    Benefits:

    • Predictable specificity: Easier to reason about which styles will take precedence.

    • Avoids specificity wars: Reduces the need for !important or overly specific selectors.

  1. Dynamic and Scalable Selectors

Problem with Multiple Selectors

When adding new selectors, you must update every instance of the rule. Assuming we had this code snippet in our stylesheet:

.header .nav li:hover,
.main .nav li:hover,
.footer .nav li:hover { 
  color: #00ff88;
}

And we wanted to add a similar hover effect to a new list inside a .nav container nested in a .sidebar div, we would do something like this:

.header .nav li:hover,
.main .nav li:hover,
.footer .nav li:hover,
.sidebar .nav li:hover { /* Added new selector */ 
  color: #00ff88;
}

Solution with :is()

With :is(), you only need to add the new selector (.sidebar) to the list:

:is(.header, .main, .footer, .sidebar) .nav li:hover {
  color: #00ff88;
}

Benefits:

  • Scalability: Easier to add or remove selectors.

  • Elegance: A cleaner syntax spanning only a single line.

  • Dynamic updates: Changes are centralized in one place.

More Complex Examples of :is()

:is() really shines when dealing with complex nested selectors.

Instead of this verbose syntax:

header p a:hover,
header p a:focus,
header p a:active,
nav p a:hover,
nav p a:focus,
nav p a:active {
    text-decoration: underline;
}

We can write this concise, cleaner version:

:is(header, nav) p a:is(:hover, :focus, :active) {
    text-decoration: underline;
}

Let’s ramp up the complexity a step further:

section section section h1,
section section article h1,
section section aside h1,
section section nav h1,
section article section h1,
section article article h1,
section article aside h1,
section article nav h1,
section aside section h1,
section aside article h1,
section aside aside h1,
section aside nav h1,
section nav section h1,
section nav article h1,
section nav aside h1,
section nav nav h1,

article section section h1,
article section article h1,
article section aside h1,
article section nav h1,
article article section h1,
article article article h1,
article article aside h1,
article article nav h1,
article aside section h1,
article aside article h1,
article aside aside h1,
article aside nav h1,
article nav section h1,
article nav article h1,
article nav aside h1,
article nav nav h1,

aside section section h1,
aside section article h1,
aside section aside h1,
aside section nav h1,
aside article section h1,
aside article article h1,
aside article aside h1,
aside article nav h1,
aside aside section h1,
aside aside article h1,
aside aside aside h1,
aside aside nav h1,
aside nav section h1,
aside nav article h1,
aside nav aside h1,
aside nav nav h1,

nav section section h1,
nav section article h1,
nav section aside h1,
nav section nav h1,
nav article section h1,
nav article article h1,
nav article aside h1,
nav article nav h1,
nav aside section h1,
nav aside article h1,
nav aside aside h1,
nav aside nav h1,
nav nav section h1,
nav nav article h1,
nav nav aside h1,
nav nav nav h1 {
    font-size: 15px;
}

Using :is() makes the approach dramatically more concise.

:is(section, article, aside, nav) :is(section, article, aside, nav) :is(section, article, aside, nav) h1 {
    font-size: 15px;
}

It generates the same number of selector combinations as the first, but with much less code. The :is() pseudo-class expands to all possible combinations behind the scenes, matching any nested structure involving those four elements where the final selector is an h1.

When Not to Use :is()

While :is() is powerful, there are cases where multiple selectors might be better:

  1. Low Browser Support: If you need to support very old browsers (e.g., Internet Explorer).

  2. Specificity Conflicts: If you intentionally want lower specificity for some selectors (use :where() instead).

  3. Overly Complex Selectors: If the :is() selector becomes harder to read than multiple selectors.

Conclusion

Use :is() when:

  • You want to reduce repetition in your CSS.

  • You need forgiving selector lists that don’t break when one selector is invalid.

  • You want to control specificity more predictably.

  • You need to scale and maintain your styles more efficiently.

By adopting :is(), you can write cleaner, more maintainable, and more robust CSS, making your stylesheets easier to work with in the long term.

I hope you enjoyed this article, and in the unlikely event that you didn’t, at least you learnt something new.

Happy coding! 🚀