An Introduction to CSS’s @supports Rule (Feature Queries)

Maria Antonietta Perna
Share

The two general approaches to tackling browsers’ uneven support for the latest technologies are graceful degradation and progressive enhancement.

Graceful degradation leverages advanced technologies to design for sophisticated user experiences and functionality. Users of less capable browsers will still be able to access the website, but will enjoy a decreased level of functionality and browsing experience.

With progressive enhancement, developers establish a baseline by designing for a level of user experience most browsers can support. Their applications provide built-in detection of browsers’ capabilities, which they use to make available more advanced functionality and richer browsing experiences accordingly.

The most widely adopted tool in a progressive enhancement approach is the Modernizr JavaScript library.

Modernizr programmatically checks if a browser supports next generation web technologies and accordingly returns true or false. Armed with this knowledge, you can exploit the new features in supporting browsers, and still have a reliable means of catering to older or noncompatible browsers.

As good as this sounds, something even better has been brewing for some time. You can perform feature detection using native CSS feature queries with the @supports rule.

In this post I’m going to delve deeper into @supports and its associated JavaScript API.

Detecting Browser Features with the @supports Rule

The @supports rule is part of the CSS3 Conditional Rules Specification, which also includes the more widespread @media rule we all use in our responsive design work.

While with media queries you can detect display features like viewport width and height, @supports allows you to check browser support for CSS property/value pairs.

To consider a basic example, let’s say your web page displays a piece of artwork that you’d like to enhance using CSS blending. It’s true, CSS blend modes degrade gracefully in non supporting browsers. However, instead of what the browser displays by default in such cases, you might want to delight users of non supporting browsers by displaying something equally special, if not equally spectacular. This is how you would perform the check for CSS blending in your stylesheet with @supports:

@supports (mix-blend-mode: overlay) {

  .example {
    mix-blend-mode: overlay;
  }

}

To apply different styles for browsers that don’t have mix-blend-mode support, you would use this syntax:

@supports not(mix-blend-mode: overlay) {

  .example {
    /* alternative styles here */
  }

}

A few things to note:

  • The condition you’re testing must be inside parentheses. In other words, @supports mix-blend-mode: overlay { ... } is not valid. However, if you add more parentheses than needed, the code will be fine. For instance, @supports ((mix-blend-mode: overlay)) is valid.
  • The condition must include both a property and a value. In the example above, you’re checking for the mix-blend-mode property and the overlay value for that property.
  • Adding a trailing !important on a declaration you’re testing for won’t affect the validity of your code.

Let’s flesh out the examples above with a small demo. Browsers with mix-blend-mode support will apply the styles inside the @supports() { ... } block; other browsers will apply the styles inside the @supports not() { ... } block.

The HTML:

<article class="artwork">
  <img src="myimg.jpg" alt="cityscape">
</article>

The CSS:

@supports (mix-blend-mode: overlay) {

  .artwork img {
    mix-blend-mode: overlay;
  }

}

@supports not(mix-blend-mode: overlay) {

  .artwork img {
    opacity: 0.5;
  }

}

Check out the demo on CodePen:

See the Pen @supports Rule Demo by SitePoint (@SitePoint) on CodePen.

Testing for Multiple Conditions at Once

When doing feature tests with @supports, you’re not limited to one test condition at any one time. Combining logical operators like and, or, and the already mentioned not operator allows you to test for multiple features at once.

The and conjunction operator tests for the presence of multiple required conditions:

@supports (property1: value1) and (property2: value2) {
  element {
    property1: value1;
    property2: value2;
  }
}

By using the disjunctive or keyword, you can test for the presence of multiple alternative features for a set of styles. This is particularly handy if some of those alternatives need vendor prefixes for their properties or values:

@supports (property1: value1) or (-webkit-property1: value1) {
  element {
    -webkit-property1: value1;
    property1: value1;
  }
}

You can also combine and with or, testing conditions in the same @supports rule:

@supports ((property1: value1) or 
          (-webkit-property1: value1)) and 
          (property2: value2) {
  element {
    -webkit-property1: value1;
    property1: value1;
    property2: value2;
  }
}

When you group a number of conditions together, the correct use of parentheses is crucial. Having and, or, and not keywords mixed together won’t work. Also, the way you group the conditions inside parentheses establishes the order in which they get evaluated. In the snippet above, the disjunctive or conditions are evaluated first, then the resulting answer is evaluated against a further required condition introduced by the and keyword.

How to group conditions for multiple checks

The not keyword lets you test for one condition at a time. For instance, the code below is not valid:

@supports not (property1: value1) and (property2: value2) {
  /* styles here... */
}

Instead, you need to group all the conditions you’re negating with the not keyword inside parentheses. Here’s the corrected version of the snippet above:

@supports not ((property1: value1) and (property2: value2)) {
  /* styles here... */
}

Finally, make sure you leave white space after a not and on both sides of an and or or.

The Operators in Action

You can apply a set of styles if the browser supports both gradients and blend modes using the following syntax (I’ve broken the code below into multiple lines for display purposes):

@supports (mix-blend-mode: overlay) and 
  (background: linear-gradient(rgb(12, 185, 242), rgb(6, 49, 64))) {

  .artwork {
    background: linear-gradient(rgb(12, 185, 242), rgb(6, 49, 64));
  }

  .artwork img {
    mix-blend-mode: overlay;
  }

}

Because some older Android browsers require the -webkit- prefix for linear gradients, let’s check for browser support by incorporating this further condition into the @supports block:

@supports (mix-blend-mode: luminosity) and 
  (
    (background: linear-gradient(rgb(12, 185, 242), rgb(6, 49, 64))) or 
    (background: -webkit-linear-gradient(rgb(12, 185, 242), rgb(6, 49, 64)))
  ) 

{

  .artwork {
    background: -webkit-linear-gradient(rgb(12, 185, 242), rgb(6, 49, 64));
    background: linear-gradient(rgb(12, 185, 242), rgb(6, 49, 64));
  }

  .artwork img {
    mix-blend-mode: luminosity;
  }

}

Let’s say your website uses luminosity and saturation blend modes which, at the time of writing, are not supported in Safari. You still want to provide alternative styles for those browsers, so here’s how you can set up the appropriate conjunctive condition using @supports not with and:

@supports not (
    (mix-blend-mode: luminosity) and 
    (mix-blend-mode: saturation)
  ) 
{

  .artwork img {
    mix-blend-mode: overlay;
  }

}

All the demos for this section are available on CodePen:

See the Pen Demos on Multiple Feature Testing with @supports by SitePoint (@SitePoint) on CodePen.

JavaScript with CSS Feature Queries

You can take advantage of CSS Feature Queries using the JavaScript CSS Interface and the supports() function. You can write the Css.supports() function in either of two ways.

The earlier and most widely supported syntax takes two arguments, i.e., property and value, and returns a boolean true or false value:

CSS.supports('mix-blend-mode', 'overlay')

Make sure you place the property and its corresponding value inside quotes. The specification makes clear that the above function returns true if it meets the following two conditions:

  • The property is a “literal match for the name of a CSS property” that the browser supports;
  • The value would be “successfully parsed as a supported value for that property”.

By literal match the specification means that CSS escapes are not processed and white space is not trimmed. Therefore, don’t escape characters or leave trailing white space, otherwise the test will return false.

The alternative, newer syntax takes only one argument inside parentheses:

CSS.supports('(mix-blend-mode: overlay)')

Using this syntax makes it convenient to test for multiple conditions with the and and or keywords.

Here’s a quick example. Let’s say you’d like to test if the browser supports the luminosity blend mode. If it does, your JavaScript will dynamically add a class of luminosity-blend to the target element, otherwise it will add a class of noluminosity. Your CSS will then style the element accordingly.

Here’s the CSS:

.luminosity-blend {
  mix-blend-mode: luminosity;
}

.noluminosity {
  mix-blend-mode: overlay;
}

If you follow the two-argument syntax, the JavaScript snippet could be as follows:

var init = function() {
  var test = CSS.supports('mix-blend-mode', 'luminosity'),
  targetElement = document.querySelector('img');

  if (test) {
    targetElement.classList.add('luminosity-blend');
  } else {
    targetElement.classList.add('noluminosity');
  }

};

window.addEventListener('DOMContentLoaded', init, false);

If you prefer the newest, single-argument syntax, simply replace the corresponding line of code above with the one below:

var test = CSS.supports('(mix-blend-mode: luminosity)')

Feel free to check out the demo:

See the Pen JavaScript API for CSS Feature Queries by SitePoint (@SitePoint) on CodePen.

Browser Support

All the latest versions of the major browsers have support for the @supports rule except for Internet Explorer 11 and Opera Mini. Is @supports ready for the real world? I’ve found the best answer to this question in Tiffany Brown’s words:

… be wary of defining mission-critical styles within @supports …
Define your base styles – the styles that every one of your targeted
browsers can handle. Then use @supports … to override and supplement
those styles in browsers that can handle newer features.

CSS Master, p.303

Conclusion

In this article, I explored native CSS browser feature detection with the @supports rule (a.k.a feature queries). I also went through the corresponding JavaScript API, which lets you check the current state of browser support for the latest CSS properties using the flexible Css.supports() method.

Browser support for CSS feature queries is good but doesn’t cover all your bases. However, if you’d like to use @supports in your projects, strategic placement of styles in your CSS document, as Tiffany Brown suggests, and the css-supports.js polyfill by Han Lin Yap can help.

If you tried out the demos in this article or have had real world experience using @supports, I’d love to hear from you.