iTranslated by AI

The content below is an AI-generated translation. This is an experimental feature, and may contain errors. View original article
⚖️

It's Finally Time to Use @supports for Fallbacks, Not Just Progressive Enhancement

に公開

Good evening to you all.
I'm the guy who usually writes about mundane technical topics.

CSS Feature Queries (@supports)

There is a thing called CSS Feature Queries. It’s that mechanism using @supports to determine whether CSS features are implemented in the various browsers visitors use to view your web page.

/* Use default italics for emphasis in browsers that don't understand `text-emphasis` */
em {
  font-style: italic;
}
/* Use emphasis dots for browsers that do understand `text-emphasis` (Japanese text only) */
@supports (text-emphasis: dot) {
  em:lang(ja) {
    font-style: initial;
    text-emphasis: dot;
  }
}

In this example, if you just wrote the styles directly, browsers that don't understand text-emphasis would lose the distinction from the body text (because the italic would be reset by the rule below). So, the approach is to wrap the latter in an @supports block to apply that style only to supporting browsers.

Many guides say "Write styles for non-supporting browsers first"

Now, if you search the web to learn about CSS Feature Queries, you'll find many explanatory articles. Most of them suggest, as in the code example above, that you should "write the descriptions for browsers that don't support a specific feature first, then wrap the styles for supporting browsers in @supports." This is what we call a Progressive Enhancement implementation.

When @supports started being implemented in browsers, there were still browsers that couldn't even understand this rule itself, so writing in this order was considered a standard, or best practice.

Nowadays, it's (almost) no problem to write "styles for non-supporting browsers last"

However, IE has long since vanished, and it is already 2025—or rather, with 2026 just a month away[1]—so in the present day, all browsers can understand CSS Feature Queries. There are some patterns where slightly older versions "don't understand selector()" or "don't understand font-format()," but I suspect that issues only occur in very rare edge cases[2].

In other words, the initial code can now be written like this without any issues.

em:lang(ja) {
  font-style: initial;
  text-emphasis: dot;
}
@supports not (text-emphasis: dot) {
  em:lang(ja) {
    font-style: revert;
  }
}

What is the benefit of writing styles for non-supporting browsers last?

You might think, "Since the result is the same either way, it doesn't matter how I write it." However, the latter approach has the advantage that when browser support improves, you just need to delete the @supports block. For example, look at this article I wrote on my blog in September:

https://jeffreyfrancesco.org/weblog/2025090701/

In that post, I wrote some CSS to calculate a unitless line-height value based on elements like font size. However, as of November 2025, Firefox has not implemented this CSS Typed OM arithmetic.

In my blog, I managed to come up with CSS that works in Firefox, but I decided to simply provide a fallback line-height with units for Firefox using the traditional method. In that case:

/* Pattern 1: Describe for non-supporting browsers first */
h1 {
  font-size: 200%;
  /* Calculated value eventually returns as a pixel <length> */
  line-height: calc(1em + (1rlh - 1rem));
}
@supports (zoom: calc(1Q / 1Q)) {
  h1 {
    /* Supporting browsers calculate a unitless <number> */
    line-height: calc((1em + (1rlh - 1rem)) / 1em);
  }
}

/* Pattern 2: Describe for non-supporting browsers last */
h1 {
  font-size: 200%;
  line-height: calc((1em + (1rlh - 1rem)) / 1em);  /* <number> */
}
@supports not (zoom: calc(1Q / 1Q)) {
  h1 {
    line-height: calc(1em + (1rlh - 1rem));  /* <length> */
  }
}

There are two ways to write this, but in the future, if Firefox supports CSS Typed OM arithmetic and the code line-height: calc(1em + (1rlh - 1rem)) becomes unnecessary, you might want to remove it. With Pattern 1, you would have to copy and paste a line from below, adjust the indentation, and then delete the @supports block... which is a hassle. Considering that effort, isn't Pattern 2, where you simply select 5 lines and press the delete key, overwhelmingly easier?

[A small aside] zoom: calc(1Q / 1Q)??

I threw this in suddenly without explanation, so some of you might be thinking 😵‍💫??
Well, you probably understand that it's a condition to test if the browser understands CSS Typed OM arithmetic, but to check this, you have to realize that dividing a value with units using calc() yields a unitless value. And,

  • Q is one of the shortest units in CSS (a single character) (others are %, s)
  • zoom is one of the properties with the fewest characters that accepts a <number> value

So, this is simply using the shortest character count declaration syntax available in @supports to check for CSS Typed OM arithmetic support.😅 So, to make it easier to understand, using something like line-height: calc(1em / 1em) is perfectly fine.

By the way, the CSS property with the fewest characters is top (three characters), but it only accepts <length-percentage> for numeric values, so it cannot be used. For example, if you used @supports (top: calc(1Q / 1Q)) in Pattern 1, it would be the same as asking "Do you support top: 1?" Therefore, no browser would match the condition, and the initial old rule would not be overwritten.

Conclusion

Since web browsers that understand the @supports rule are now mainstream, the point is that it's generally better to write fallbacks for non-supporting browsers inside an @supports not (…) {} block, rather than writing the styles for supporting browsers inside @supports (…) {}. This makes organizing your CSS easier as browser feature support advances.

Of course, there might be cases where this doesn't work well (e.g., if it increases the amount of code), but in those cases, you can just be flexible. Regardless, I don't think you lose anything by keeping this in mind when writing your CSS.

...I didn't quite manage to wrap this up well at the end. Tears.

脚注
  1. I don't acknowledge that, but it's coming anyway, so there's nothing I can do (´・_・`) ↩︎

  2. For example, if you try to detect :is() selector support using selector(), you will miss Safari v14.0.x (as the former was supported in v14.0, while the latter was supported in v14.1) ↩︎

Discussion