Yesterday I took some time to rework my Houdini-powered CSS Gradient Border Animation Demo to include a fallback for non-Houdini browsers.
The plan of attack was pretty straightforward:
- Manual frame-by-frame animations for non-Houdini browsers
- Automagic Houdini-powered animations for browser with
@property
support
Only problem with that approach is that there’s currently no way to use @supports
to directly detect whether a browser supports Houdini’s @property
or not, so I got creative with @supports
…
~
🎩 Houdini, ain't that a magician?
Houdini is a set of low-level APIs that exposes parts of the CSS engine, giving developers the power to extend CSS by hooking into the styling and layout process of a browser’s rendering engine. Houdini is a group of APIs that give developers direct access to the CSS Object Model (CSSOM), enabling developers to write code the browser can parse as CSS, thereby creating new CSS features without waiting for them to be implemented natively in browsers.
It really is magic, hence it's name Houdini. I'd recommend this slidedeck and this video to get you started
~
Table of Contents
~
# What I want
Ideally I want to check for support for @property
using something like this:
/* Syntax Proposal */
@supports (@property) {
…
}
/* Syntax Proposal, Expanded to check for certain descriptor support */
@supports (@property { syntax: "<angle>" }) {
…
}
/* Alternative Syntax Proposal */
@supports descriptor(@property, syntax: "<angle>") {
…
}
Unfortunately this is currently not possible and considered a shortcoming of CSS @supports
. The issue is being discussed in CSSWG Issue 2463.
~
# Getting creative with @supports
💁♂️ Note that I’ll be using browser names here (e.g. Firefox), even though the statements apply to their underlying rendering engines (e.g. Gecko). And when talking about “Chromium” I mean all browsers that are based on it (i.e. Google Chrome, Microsoft Edge, Brave, etc.)
As Chromium (Blink) currently is the only browser that supports @property
, we need to create a @supports
rule that targets Chromium only. While at it let’s also create @supports
rules to target only Safari (WebKit), Firefox (Gecko), or a combination thereof.
Looking at what each browser uniquely supports, these three rules can be used as a starting point:
- Only Firefox supports
-moz-appearance: none
- Only Safari supports the
:nth-child(An+B [of S]?)
selector. - Both Chromium and Firefox support
contain: paint
👉 By combining/excluding these rules you can target a set of browsers, or only one specific one.
# UPDATE 2021.06.29 — It came to my attention that it’s possible to detect @property
support by checking for background: paint(worklet);
support:
💡 A more robust approach to detect @property support is to check for support for the Paint API: any browser that supports #CSSHoudini Paint Worklets also supports the Properties and Values API (and vice versa). pic.twitter.com/3hBYlhmIjt
— Bramus! (@bramus) June 29, 2021
The further contents of this post still stand though, as the separate @supports
rules described below do their thing. However, if you want to check for Houdini @property
support, use the one mentioned above.
~
# The @supports
rules
🚨 Warning: the following @supports
rules are a hacky workaround that reeks a lot like browser sniffing. They’re very fragile as rendering engines may add support for those properties/selectors over time. Things can — and will — break in the future. By the time you read this, this post will most likely no longer be accurate. Be ye warned.
# Targeting Firefox Only
Firefox is the only browser that supports -moz-appearance: none
/* Firefox Only */
@supports (-moz-appearance: none) {
…
}
# Targeting Not-Firefox (i.e. Chromium + Safari)
By negating the selector only Firefox supports you can target Chromium + Safari:
/* Chromium + Safari */
@supports (not (-moz-appearance: none)) {
…
}
# Targeting Safari Only
Safari is the only browser that supports the complex :nth-child(An+B [of S]?)
selector — which itself allows you to create a :nth-of-class
-like selector — so you can rely on that. Using selector()
function in @supports
we can check whether the browser supports it or not.
/* Safari Only */
@supports selector(:nth-child(1 of x)) {
…
}
Note that MobileSafari supports this selector()
function only since iOS 14.6 which, at the time of writing, is only the very latest non-beta version.
If you also want to cater for earliers MobileSafari versions, add an extra check for (-webkit-touch-callout: none)
which only MobileSafari supports:
/* (Safari + MobileSafari >= 14.6) or (All MobileSafari versions) */
@supports (selector(:nth-child(1 of x))) or (-webkit-touch-callout: none) {
…
}
The extra parens around selector()
are crucial here. When omitted MobileSafari prior to version 14.6 breaks on parsing the @supports
rule, ignoring it entirely.
# UPDATE 2021.09.06 — Alternatively, as pointed out by reader Šime Vidas, you can detect Safari by checking for -webkit-named-image()
support:
Apple added a non-standard -webkit- function after the Chrome fork, so why not use it to detect Safari in CSS. Is this … web development? 😁https://t.co/vdygFwhnQc pic.twitter.com/cJPWsTsDRK
— Šime Vidas (@simevidas) May 14, 2020
# Targeting Not-Safari (i.e. Chromium + Firefox)
You could negate the rules above to target Chromium and Firefox, but instead I chose to check for support for contain: paint
which both browsers support:
/* Chromium + Firefox */
@supports (contain: paint) {
…
}
The reason why I choose this selector becomes clear in the next part 😉
# Targeting Chromium Only
To target Chromium only you can select both Chromium and Firefox (using @supports (contain: paint)
), and then exclude Firefox from there (using @supports (not (-moz-appearance: none))
).
Combined you get this @supports
rule:
/* Chromium Only */
@supports (contain: paint) and (not (-moz-appearance: none)) {
…
}
# Targeting Not-Chromium (i.e. Safari + Firefox)
As @supports
understands OR-logic, you can combine the rules for both Safaris and Firefox into one:
/**/
@supports (-webkit-touch-callout: none) or (selector(:nth-child(1 of x))) or (-moz-appearance: none) {
…
}
~
# Combined Demo
Below is a combined demo. The background-color
of the body should be:
- Chromium:
blue
- Firefox:
lime
- Desktop Safari:
red
- Mobile Safari:
orange
See the Pen CSS @support rules to target only Firefox / Safari / Chromium by Bramus (@bramus) on CodePen.
Additionally you can also check this combined image (“cheat sheet”):
~
# In Closing
I’m glad I could flesh things out and create @supports
rules to target the three major rendering engines. Let’s hope we can drop these workarounds soon, when CSSWG Issue 2463 gets resolved (and implemented) 🙂
~
To help spread the contents of this post, feel free to retweet its announcement tweet:
CSS `@supports` rules to target only Firefox / Safari / Chromium
— Bram.us (@bramusblog) June 23, 2021
🔗 https://t.co/lV2Ubz6cVm
🏷 #CSS #atSupports #FeatureQueries #ProgressiveEnhancement pic.twitter.com/84GtuC0OCc
Follow @bramus (= me, the author) and/or @bramusblog (= the feed of this blog) on Twitter to stay-up-to date with future posts. RSS also available.
~
Thank me with a coffee.
I don\'t do this for profit but a small one-time donation would surely put a smile on my face. Thanks!
To stay in the loop you can follow @bramus or follow @bramusblog on Twitter.
Hi Bramus, it looks like this is not working. I have both Brave (Chromium) and Safari open next to each other on my desktop and the “combined demo” are both showing blue.. (Safari should be red?). Has something changed with the specs on either of these browsers since 2021?
As mentioned in the post, these tests are really fragile and could stop working any time. I guess that time has come, as more browsers support many new features.