Applying CSS Conditionally: Testing for Property Support with Feature Queries

Feature queries let us apply CSS rules when the browser supports a particular property and value combination. As with media queries, feature queries consist of two parts: the @supports CSS rule, and a DOM-based API for use with JavaScript.

Why might we use @supports ? Here’s a scenario: as originally specified , display allowed four possible values: block , inline , List-item , and none . Later specifications added table-* values, flex , and grid . With @supports , we can define CSS rules that will be applied only when the browser supports display: grid :

@supports ( display: grid ) {

.gallery {

display: grid;

grid-template-columns: repeat( 4, auto );

}

}

To define a condition, wrap the property and value you’d like to test in a set of parentheses as shown. Both portions are required. A condition such as @supports (hyphens) won’t work. You can, however, use a CSS keyword such as unset or initial as part of the test-such as @supports (hyphens: initial) .

To combine conditions, use the and keyword. For example, if you wanted to apply styles when both the text-decoration-color and text-decoration-style are supported, you could use the following:

@supports ( text-decoration-color: #c09 ) and ( text-decoration-style: double ) {

.title {

font-style: normal;

text-decoration: underline double #f60;

}

}

The @supports syntax also allows disjunctions using the or keyword. Disjunctions are especially useful for testing vendor-prefixed property support. Older versions of WebKit- based browsers require a vendor prefix for flexible box layout support. We can augment our @supports condition to take that into account:

@supports ( display: flex ) or ( display: -webkit-flex ) {

nav ul {

display: -webkit-flex;

display: flex;

}

}

Finally, we can also define a collection of styles if a condition isn’t supported by using the not keyword:

@supports not ( display: grid ) {

nav {

display: flex;

}

}

The not keyword can only be used to negate one condition at a time. In other words, @supports not (text-decoration-color: #c09) and (text-decoration-style: double) is not valid. But you can combine two tests into a single condition by using an outer set of parentheses: @supports not ((text-decoration-color: #c09) and (text-decoration-style: double)) .

Very old browsers, such as Internet Explorer, lack support for both @supports and properties you might wish to query, such as float: inline-start . For those browsers, we can leverage CSS error handling and the cascade instead. CSS ignores rules that it can’t parse, and the last- defined rule wins. Below is an example using the float property:

img {

float: left; /* Browsers that support the old float values */

float: inline-start; /* Browsers that support newer logical values */

}

Using error handling and the cascade often works well enough that you can forgo using @supports altogether. You may, however, need to use @supports to isolate and override declarations supported by both older and newer browsers. Consider the following CSS:

nav ul {

text-align: center; padding: 0;

}

nav li {

display: inline-block; min-width: 20rem

}

nav li:not( :last-child ) {

margin: 0 1.5rem 0 0;

}

@supports ( display: grid ) {

nav ul {

display: grid;

grid-template-columns: repeat( auto-fit, 20rem );

justify-content: center; gap: 1.5rem;

}

/* Undo all of the styles from above */ nav li:not( :last-child ), nav li {

display: initial;

margin: 0;

min-width: unset;

}

}

In this case, we’ve used @supports to remove the margin for nav Li only when the browser supports CSS Grid. Browsers that lack support for feature queries ignore the entire block.

1. Determining Selector Support with selector()

Originally designed to test support of properties and values, the CSS Conditional Rules

Module Level 4 specification expands the syntax of @supports to include selectors using seLector() . Here’s an example:

@supports selector( :blank ) {

input:not(:blank):invalid {

background: pink;

}

}

In browsers that support the :blank pseudo-class (and to date, no browser does), <input> elements that contain invalid data but are not blank will have a pink background.

Remember that CSS ignores rules and selectors that it doesn’t understand. In other words, you probably don’t need to use seLector() . If you do, make sure your site degrades gracefully.

2. CSS.supports DOM API

Feature queries also have an API: css.supports() . css.supports() always returns a Boolean ( true or false ) value depending on whether or not the browser supports that property and value combination.

CSS.supports() accepts a parentheses-wrapped CSS declaration as its argument. For example:

CSS.supports( ‘( text-decoration: underline wavy #e91e63 )’ );

If the browser supports this syntax for text-decoration , CSS.supports returns true . Otherwise, it returns false .

We can test multiple conditions using conjunctions (the and keyword) or disjunctions (the or keyword). CSS.supports also allows negation using the not keyword. For example, we can test whether a browser supports display: -webkit-flex or display: flex using the following:

CSS.supports( ‘( display: -webkit-flex ) or ( display: flex )’ );

Most browsers treat parentheses as optional when testing a single property and value combination (versions of Microsoft Edge 18 and under are an exception). When testing support for multiple conditions, each one must be wrapped in parentheses, as we’ve done here. Failing to do so means that css.supports() may return a false negative.

You can also use css.supports with seLector() to test selector support, as shown below:

const canUsels = CSS.supports( ‘selector( :is() )’ );

console.log( canUsels ); // Logs true or false

Enclose the entire condition in quotes to prevent a JavaScript ReferenceError that selector is undefined .

Selectors that use functional notation— :is() , :where() and :has() —should include parentheses. When testing support for :not() , you’ll also need to include an argument (it’s optional for the other selectors):

CSS.supports( ‘selector( :not() )’); // Returns false

CSS.supports(‘selector( :not( :last-child ) )’); // Returns true

Although this feature isn’t well-documented, it is widely supported in browsers.

Source: Brown Tiffany B (2021), CSS , SitePoint; 3rd edition.

Leave a Reply

Your email address will not be published. Required fields are marked *