Feature Detection Strategies

What to do when a CSS feature is not supported cross-browser.

Code

Every CSS feature available in every browser at the same time? Not a reality. Features are adopted by browsers at their own pace, with different priorities. Can we make use of a feature before it lands in all browsers? Sure. These days we can check if a feature is supported with @supports, but we have other options. Let’s take a closer look.

1: Redeclaration

Instead of adding a @supports block, sometimes it is easier to simply repeat the declaration. As Bramus Van Damme comments in his The Large, Small, and Dynamic Viewports article:

… you could also do without the check, as browsers discard declarations they don’t understand.

After all, this is how we adopted transparency in colors many years ago. We put a fallback color with rgb() or hex notation in place, and the desired rgba() color immediately afterwards.

Using those new viewport units as an example, we set the vb and the dvb in succession:

body {
	block-size: 100vb;
	block-size: 100dvb;
}

Browsers that only support the vb unit will use that and ignore the dvb declaration.[1]

2: Feature Queries

Sure enough, there are limits to redeclaration. Feature queries come into play when you have to reset things browsers do understand. Before gap (initially grid-gap) was supported in grid layout, we used margins to generate space between grid items. To future-proof things, we added a test for gap support using a @supports block, which included the proper way to do gaps, and the removal of the hacks. If you did those things without a feature query, the fallback margins would have vanished even in those browsers that still needed the hack, because all browsers understand the margin declaration.

If we recreate the example above with feature queries, it looks more verbose:

body {
	block-size: 100vb;
}
@supports (block-size: 1dvb) {
	body {
		block-size: 100dvb;
	}
}

Those are the two ways. That’s it, right? For the most part, yes. But in this particular case, we have a third option.

3: Feature Queries & Custom Properties

Our case being: We are querying the support of a unit (like dvb) and not a property (like gap). That’s why, instead of redeclaring it, we can store the thing we test for in a custom property, and assign just that.

body {
	--block-size: 100vb;
	block-size: var(--block-size);
}
@supports (block-size: 1dvb) {
	body {
		--block-size: 100dvb;
	}
}

Why would you do that? This is even more verbose than the last one. True, but it becomes beneficial the moment you have to use the custom property in multiple places, unlike in this example, where it is assigned just once. And as soon as calc() comes into play, it could lead to duplication of potentially complex code. Instead of writing that calculation twice (as you would have to when using method 1 or 2), making the custom property containing the feature query result part of a single calculation is usually better.

Comparison

Ultimately, you have to choose what works in your particular case. The three examples described in this article all produce the same result. You can see for yourself by heading over to CodePen, where I’ve placed them side by side.


Footnotes

  1. I’m using this example only to illustrate the point; it is quite likely that both vb and dvb landed in browsers at the same time, meaning the second declaration always wins. With the vh/dvh combo on the other hand, there has been a time where only the former was supported. ↩︎