CSS @​supports rules to target only Firefox / Safari / Chromium

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

  1. What I want
  2. Getting creative with @supports
  3. The @supports rules
  4. Combined Demo
  5. In Closing

~

# 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:

  1. Only Firefox supports -moz-appearance: none
  2. Only Safari supports the :nth-child(An+B [of S]?) selector.
  3. 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:

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:

# 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:

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.

~

Did this help you out? Like what you see?
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!

BuymeaCoffee (€3)

To stay in the loop you can follow @bramus or follow @bramusblog on Twitter.

Published by Bramus!

Bramus is a frontend web developer from Belgium, working as a Chrome Developer Relations Engineer at Google. From the moment he discovered view-source at the age of 14 (way back in 1997), he fell in love with the web and has been tinkering with it ever since (more …)

Unless noted otherwise, the contents of this post are licensed under the Creative Commons Attribution 4.0 License and code samples are licensed under the MIT License

Join the Conversation

3 Comments

  1. 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?

    1. 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.

Leave a comment

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

This site uses Akismet to reduce spam. Learn how your comment data is processed.