{"id":263984,"date":"2017-12-21T09:02:59","date_gmt":"2017-12-21T16:02:59","guid":{"rendered":"http:\/\/css-tricks.com\/?page_id=263984"},"modified":"2022-12-02T09:58:31","modified_gmt":"2022-12-02T17:58:31","slug":"is","status":"publish","type":"page","link":"https:\/\/css-tricks.com\/almanac\/selectors\/i\/is\/","title":{"rendered":":is"},"content":{"rendered":"\n

The pseudo-select :is()<\/code> in CSS allows you to write compound selectors more tersely. For example, rather than writing:<\/p>\n\n\n\n

ul li,\nol li {}<\/code><\/pre>\n\n\n\n

We could write:<\/p>\n\n\n\n

:is(ul, ol) li {}<\/code><\/pre>\n\n\n\n\n\n\n\n

This can make quick work of otherwise extremely verbose, complex, and error prone selectors. See:<\/p>\n\n\n\n

:is(section, article, aside, nav) :is(h1, h2, h3, h4, h5, h6) {\n  color: #BADA55;\n}\n\n\/* ... which would be the equivalent of: *\/\nsection h1, section h2, section h3, section h4, section h5, section h6, \narticle h1, article h2, article h3, article h4, article h5, article h6, \naside h1, aside h2, aside h3, aside h4, aside h5, aside h6, \nnav h1, nav h2, nav h3, nav h4, nav h5, nav h6 {\n  color: #BADA55;\n}<\/code><\/pre>\n\n\n\n

You can attach the pseudo-selector to an element like you’d expect. Like to select a specific type of element when it has either of two classes:<\/p>\n\n\n\n

div:is(.cool, .fun) {\n  color: red;\n}\n\n\/*\n<div class=\"cool\">match<\/div>\n<div class=\"fun\">match<\/div>\n<p class=\"fun\">not match<\/p>\n*\/<\/code><\/pre>\n\n\n

Hey, isn’t that like CSS preprocessing?<\/h3>\n\n\n

Simplifying selectors with :is()<\/code> is similar to how CSS preprocessors handle nested rules:<\/p>\n\n\n\n

\/* SCSS written like this: *\/\ndiv, p, ul, ol {\n  span {\n    \/* ... *\/\n  }\n}\n\n\/* after processing becomes: *\/\ndiv span, p span, ul span, ol span {\n  \/* ...*\/\n}\n\n\/* which is a lot like the effect of :is()! *\/<\/code><\/pre>\n\n\n\n

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

Specificity of :is()<\/code><\/h3>\n\n\n

According to the CSS4 Working Draft<\/a>:<\/p>\n\n\n\n

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().<\/p><\/blockquote>\n\n\n\n

That means that the specificity of :is()<\/code> gets auto-upgraded<\/em> to the most specific item in the list of arguments provided:<\/p>\n\n\n\n

\/* This has higher precedence... *\/\n:is(ol, .list, ul) li { \/* ... *\/ }\n\n\/* ...than this, even though this is later... *\/\nol li  { \/* ... *\/ }\n\n\/* ...because :is() has the weight of it's heaviest selector, which is `.list`! *\/<\/code><\/pre>\n\n\n

Forgiving selector lists<\/h3>\n\n\n

Normally if any part of a selector is invalid, the entire block is thrown out:<\/p>\n\n\n\n

p, p::not-real {\n  color: red; \/* nothing will be red, as ::not-real is an invalid selector *\/\n}<\/code><\/pre>\n\n\n\n

I’ve heard browsers might chill out on this in the future, but we aren’t there yet. If you are hoping to keep that and not have to break it into two separate blocks, is()<\/code> can help because it’s “forgiving”:<\/p>\n\n\n\n

:is(p, p::not-real) { \/* this is fine *\/\n  color: red;\n}<\/code><\/pre>\n\n\n\n

I always think about ::selection<\/code> and the ::-moz-selection<\/code> vendor prefix version when I think of invalid comma-separated selectors, but…<\/p>\n\n\n\n

:is(::selection, ::-moz-selection) { \/* this doesn't work in Chrome for some reason 🤷‍♀️ *\/\n  background: yellow;\n}<\/code><\/pre>\n\n\n

Browser support<\/h3>\n\n

This browser support data is from Caniuse<\/a>, which has more detail. A number indicates that browser supports the feature at that version and up.<\/p><\/div>

Desktop<\/h4>
Chrome<\/span><\/th>Firefox<\/span><\/th>IE<\/span><\/th>Edge<\/span><\/th>Safari<\/span><\/th><\/tr><\/thead>
88<\/span><\/td>78<\/span><\/td>No<\/span><\/td>88<\/span><\/td>14<\/span><\/td><\/tr><\/table><\/div>

Mobile \/ Tablet<\/h4>
Android Chrome<\/span><\/th>Android Firefox<\/span><\/th>Android<\/span><\/th>iOS Safari<\/span><\/th><\/tr><\/thead>
123<\/span><\/td>124<\/span><\/td>123<\/span><\/td>14.0-14.4<\/span><\/td><\/tr><\/table><\/div><\/div>\n\n\n\n

To get the best support, you might look at also using :matches<\/code> (with vendor-prefixed :any<\/code> filling in some gaps) for the general functionality. And, :not()<\/a><\/code> is another pseudo-class that can assist with matching.<\/p>\n\n\n\n

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

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.<\/p>\n\n\n\n

\/* These are deprecated, but still necessary in some browsers: *\/\n:-moz-any(div, p) > em { \/* ... *\/ }\n:-webkit-any(div, p) > em { \/* ... *\/ }\n\n\/* Has been replaced by :is() in CSS4, but still supported \nby some browsers with experimental features enabled *\/\n:matches(div, p) > em { \/* ... *\/ }\n\n\/* Latest syntax *\/\n:is(div, p) > em { \/* ... *\/ }<\/code><\/pre>\n\n\n\n