Detecting CSS features with @supports

With a variety of different browsers (and rapidly growing number of versions) out there feature detecting is a necessity for web developers. It keeps us from coding into the blue — while relying on the misbelief that all users regularly update their browser. In consequence adding checks into the code whether a specific feature is available helps us to work around vendor prefixes, enables us to use polyfills when necessary, and shields users from bad user experience.

Libraries like Modernizr assist us with detecting JavaScript and CSS features. But those libraries actually step in where browsers can do a good-enough job. Detecting programmatic features is easily done with plain JavaScript. When looking for Custom Elements support you can just do

if('registerElement' in document){ … }

Native feature detection for CSS isn’t that well-established to date, but available in all major browsers (implying the days of IE is numbered). It comes in two flavors: a pure CSS directive named @supports and a JavaScript helper function CSS.supports().

The CSS @supports directive

The @supports directive works much alike media queries. You specify a condition, in this case a feature you want to use, and apply styles as usual. The example shows how to detect support for CSS Scroll Snap Points, which is (at the time of writing) implemented in Firefox:

@supports (scroll-snap-type:mandatory){
.scroll {
scroll-snap-type: mandatory;
...
}
}

The great thing is, that you can use all sorts of logical operations in CSS feature detection, like checking for alternatives: CSS Scroll Snap Points have vendor-prefixed implementations in Microsoft Edge/IE and Safari. To target any of those cases, we could simply add logical conditions to the above example:

@supports (-webkit-scroll-snap-type: mandatory) or
(-ms-scroll-snap-type: mandatory) or
(scroll-snap-type: mandatory) {
.scroll {
-webkit-scroll-snap-type: mandatory;
-ms-scroll-snap-type: mandatory;
scroll-snap-type: mandatory;
...
}
}

Or we could target the case where a feature is missing to define a fallback, as for Chrome, just by using the not keyword:

@supports not ((-webkit-scroll-snap-type: mandatory) or
(-ms-scroll-snap-type: mandatory) or
(scroll-snap-type: mandatory)) {
.scroll {
/* define a fallback */
...
}
}

Using CSS.supports() in JavaScript

The window.CSS.supports() functions does exactly the same thing in JavaScript. It checks whether a feature is available and gives a boolean result. Following the CSS Snap Points example from above, we could simply decide whether to we had load a polyfill by using

if(!CSS.supports("scroll-snap-type", "mandatory"){
// load polyfill
}

In this simple case CSS.supports() accepts two parameters: the name of the CSS feature we want to test and its value. More complex checks, for example when we’d like to detect multiple features at once combined with Boolean operator, don’t work that way. But there is an even easier to use alternative: we can use CSS.supports() with only one string parameter with exactly the same feature detection syntax as @supports would accept:

if(!CSS.supports("(-webkit-scroll-snap-type: mandatory) or
(-ms-scroll-snap-type: mandatory) or
(scroll-snap-type: mandatory)"){
// load polyfill
}

Happy feature detecting!

Thanks for reading. Best way to stay tuned is following us on Twitter. Comments are as always very welcome.

The tech staff of OneBitAhead GmbH, putting the web stack to work. Here to discuss daily bits & bytes. #javascript #nodejs #webcomponents #rdbms #php

The tech staff of OneBitAhead GmbH, putting the web stack to work. Here to discuss daily bits & bytes. #javascript #nodejs #webcomponents #rdbms #php