Frontend Development

Mastering CSS :has() and :is() Selectors for Complex Component Styling

For years, CSS developers have faced a persistent limitation: the inability to select a parent element based on its children. We relied heavily on JavaScript to add helper classes or used cumbersome SASS mixins to overcome this gap. However, the introduction of the :has() and :is() pseudo-classes has revolutionized how we write styles. These powerful tools allow for more expressive, maintainable, and efficient code. In this post, we will explore how to leverage these selectors to tackle complex UI challenges.

Understanding the :is() Selector

The :is() pseudo-class is essentially a grouping selector. It allows you to match elements that match any of the selectors in its argument list. Before :is(), developers often repeated selectors to apply shared styles, leading to verbose and redundant code.

Consider a scenario where you want to style all headings within a specific container differently. Without :is(), you might write:


.container h1 {
  color: blue;
}

.container h2 {
  color: blue;
}

.container h3 {
  color: blue;
}

With :is(), you can condense this into a single, clean rule:


.container :is(h1, h2, h3) {
  color: blue;
}

This not only reduces file size but also improves readability. Furthermore, :is() inherits the specificity of its most specific argument. This is crucial for understanding how it interacts with other selectors, ensuring your styles cascade exactly as intended without unexpected specificity wars.

The Game Changer: :has() Selector

If :is() is about grouping, :has() is about context. Often referred to as the "parent selector," :has() selects an element if it contains at least one element that matches the selector provided in its argument. This capability opens up a world of purely CSS-based solutions for states that previously required JavaScript.

Let’s look at a practical example: Styling a form group based on whether its input field is valid. Traditionally, you would need an event listener on the input to add a class to the parent form-group. With :has(), this is automatic:


/* Style the label if the input inside the form-group is valid */
.form-group:has(input:valid) .label {
  color: green;
}

/* Style the border of the form-group if its input has an error */
.form-group:has(input:invalid) {
  border-color: red;
}

This selector is incredibly powerful for building accessible components. For instance, you can visually highlight a card when a user interacts with any link within it:


.card:has(:focus-visible) {
  box-shadow: 0 0 0 3px rgba(66, 153, 225, 0.5);
}

Nesting and Combinators

:has() supports all standard combinators, such as + (next sibling) and ~ (general sibling). This allows for complex logical expressions. For example, you can style a navigation item if it has a dropdown menu that is currently open:


nav li:has(~ .dropdown-menu.open) {
  background-color: #f0f0f0;
}

Browser Support and Best Practices

As of 2024, both :has() and :is() enjoy broad support in modern browsers, including Chrome, Firefox, Safari, and Edge. However, if you must support legacy browsers like Internet Explorer, consider using PostCSS plugins like postcss-nesting or falling back to JavaScript solutions.

When using these selectors, keep the following best practices in mind:

  • Avoid over-nesting: While :has() is powerful, deeply nested selectors can become hard to debug.
  • Test accessibility: Ensure that visual cues added via :has() do not rely solely on color, as this can impact users with visual impairments.
  • Performance: Complex :has() queries can be computationally expensive. Benchmark your styles if you are building large-scale applications.

Conclusion

The :is() and :has() selectors mark a significant evolution in CSS capabilities. By allowing for better grouping and contextual styling, they empower developers to write cleaner, more semantic, and more maintainable stylesheets. As these features become standard across all platforms, mastering them is no longer optional—it is essential for any frontend developer looking to stay ahead of the curve. Embrace these tools to reduce your JavaScript dependency and create more robust user interfaces.

Share: