Avatar of Geoff Graham
Geoff Graham on

AWS Amplify - the fastest, easiest way to develop mobile and web apps that scale.

:is() is the current name for the “Matches-Any” pseudo-class in the CSS4 working draft.

Originally, this pseudo-class was named :any() and was implemented with limited vendor-specific support:

/* Never actually worked */
:any(div, p) > em { /* ... */ }

The “Matches-Any” pseudo-class name was then changed to :matches() in early versions of the CSS4 working draft, with additional (incomplete) support being given to some browsers.

/* Sort of works */
:matches(div, p) > em { /* ... */ }

Finally, the current working draft has renamed this pseudo-class to :is(), which is currently unsupported in browsers.

/* Will work in the future? */
:is(div, p) > em { /* ... */ }

The goal of the “Matches Any” selector (with all of it’s names) is to make complex groupings of selectors easier to write.


/* Theoretical until CSS4 support finalized */
:is(section, article, aside, nav) :is(h1, h2, h3, h4, h5, h6) {
  color: #BADA55;

/* ... which would be the equivalent of: */
section h1, section h2, section h3, section h4, section h5, section h6, 
article h1, article h2, article h3, article h4, article h5, article h6, 
aside h1, aside h2, aside h3, aside h4, aside h5, aside h6, 
nav h1, nav h2, nav h3, nav h4, nav h5, nav h6 {
  color: #BADA55;

Hey, isn’t that like CSS preprocessing?

Simplifying selectors with :is() is indeed similar to how CSS preprocessors handle nested rules:

/* SCSS written like this: */
div, p, ul, ol {
  span {
    /* ... */

/* after processing becomes: */
div span, p span, ul span, ol span {
	/* ...*/

/* which is a lot like the effect of :is()! */

But beware! Preprocessors, like Sass, “unroll” your nested rules into a list of easily-understood selectors. :is() will handle specificity rules a little differently:

Specificity is Interesting

According to the CSS4 Working Draft:

The specificity of the :is() pseudo-class is replaced by the specificity of its most specific argument. Thus, a selector written with :is() does not necessarily have equivalent specificity to the equivalent selector written without :is().

That means that the specificity of :is() gets auto-upgraded to the most specific item in the list of arguments provided:

/* This has higher precedence... */
:is(ol, .list, ul) li { /* ... */ }

/* ...than this, even though this is later... */
ol li  { /* ... */ }

/* ...because :is() has the weight of it's heaviest selector, which is `.list`! */

Browser Support

We alluded to this earlier, but :is() has no browser support at the moment. It was introduced in the Editor’s Draft 1 of the CSS Selectors Level 4 Specification. That means things are still getting shaped out making it a little early for browsers to rally around a concept like this.

That said, we do have great browser support in the form of :matches (with vendor-prefixed :any filling in some gaps) for the general functionality. And, of course, :not is another pseudo-class that can assist with matching.

What’s interesting to note is that :is() was introduced after :matches which was introduced after :any. It’s sort of like :any is being replaced by :matches which is being replaced by :is(), with the details changing along the way. Always neat to see how these things evolve.

To get maximum support for “Matches-Any” requires using a mix of the historical names, as browser handling is currently a hodgepodge of vendor-prefixes and experimental settings at this point.

/* These are deprecated, but still necessary in some browsers: */
:-moz-any(div, p) > em { /* ... */ }
:-webkit-any(div, p) > em { /* ... */ }

/* Has been replaced by :is() in CSS4, but still supported 
by some browsers with experimental features enabled */
:matches(div, p) > em { /* ... */ }

/* Doesn't work yet, but for future support, maybe add this? */
:is(div, p) > em { /* ... */ }

See the Pen examples of :any pseudo-class by CSS-Tricks (@css-tricks) on CodePen.