Recipes for Detecting Support for CSS At-Rules

The @supports at-rule has been extended several times since its initial release. Once only capable of checking support for property/value pairs, it can now check for a selector using the selector() wrapper function and different font formats and techs using font-format() and font-tech(), respectively. However, one feature the community still longs for is testing other at-rules support.

@supports at-rule(@new-rule) {
  /* @new-rule is supported */
}

The CSSWG decided in 2022 to add the prior at-rule() wrapper function. While this is welcome and wonderful news, here we are two years later and we don’t have a lot of updates on when it will be added to browsers. So, how can we check for support in the meantime?

Funny coincidence: Just yesterday the Chrome team changed the status from “new” to “assigned” as if they knew I was thinking about it.

Looking for an answer, I found this post by Bramus that offers a workaround: while we can’t check for a CSS at-rule in the @supports at-rule, we can test a property that was shipped with a particular at-rule as a substitute, the thinking being that if a related feature was released that we can test and it is supported, then the feature that we’re unable to test is likely to be supported as well… and vice versa. Bramus provides an example that checks support for the animation-timeline property to check if the @scroll-timeline at-rule (which has been discontinued) is supported since the two were shipped together.

@supports (animation-timeline: works) {
  /* @scroll-timeline is supported*/
}

/* Note: @scroll-timeline doesn't exist anymore */

Bramus calls these “telltale” properties, which is a fun way to think about this because it resembles a puzzle of deduction, where we have to find a related property to check if its at-rule is supported.

I wanted to see how many of these puzzles I could solve, and in the process, know which at-rules we can reliably test today. So, I’ve identified a full list of supportable at-rules that I could find.

I’ve excluded at-rules that offer no browser support, like @color-profile, @when, and @else, as well as deprecated at-rules, like @document. Similarly, I’m excluding older at-rules that have enjoyed wide browser support for years — like @page, @import, @media, @font-face, @namespace and @keyframes — since those are more obvious.

@container size queries (baseline support)

Testing support for size queries is fairly trivial since the module introduces several telltale properties, notably container-type, container-name and container. Choose your favorite because they should all evaluate the same. And if that property is supported, then @container should be supported, too, since it was introduced at the same time.

@supports (container-type: size) {
  /* Size queries are supported! */
}

You can combine both of them by nesting a @supports query inside a @container and vice versa.

@supports (container-type: size) {
  @container (width > 800px) {
    /* Styles */
  }
}

@container style queries (partial support)

Size queries give us a lot of telltale properties to work with, but the same can’t be said about style queries. Since each element has a style containment by default, there isn’t a property or value specific to them. We can work around that by forgetting about @supports and writing the styles inside a style query instead. Style queries work in supporting browsers but otherwise are ignored, so we’re able to write some base styles for older browsers that will be overridden by modern ones.

.container {
  --supports-style-queries: true;
}

.container .child {
  /* Base styles */
}

@container style(--supports-style-queries: true) {
  /* Container queries are supported! */
  .child {
    /* We can override the base styles here */
  }
}

@counter-style (partial support)

The @counter-style at-rule allows us to make custom counters for lists. The styles are defined inside a @counter-style with custom name.

@counter-style triangle {
  system: cyclic;
  symbols: ‣;
  suffix: " ";
}

ul {
  list-style: triangle;
}

We don’t have a telltale property to help us solve this puzzle, but rather a telltale value. The list-style-type property used to accept a few predefined keyword values, but now supports additional values since @counter-style was introduced. That means we should be able to check if the browser supports <custom-ident> values for list-style-type.

@supports (list-style: custom-ident) {
  /* @counter-style is supported! */
}

@font-feature-values (baseline support)

Some fonts include alternate glyphs in the font file that can be customized using the @font-feature-values at-rule. These custom glyphs can be displayed using the font-variant-alternatesl, so that’s our telltale property for checking support on this one:

@supports (font-variant-alternates: swash(custom-ident)) {
  /* @font-feature-values is supported! */
}

@font-palette-values (baseline support)

The same concept can be applied to the @font-palette-values at-rule, which allows us to modify multicolor fonts using the font-palette property that we can use as its telltale property.

@supports (font-palette: normal) {
  /* @font-palette-values is supported! */
}

@position-try (partial support)

The @position-try at-rule is used to create custom anchor fallback positions in anchor positioning. It’s probably the one at-rule in this list that needs more support since it is such a new feature. Fortunately, there are many telltale properties in the same module that we can reach for. Be careful, though, because some properties have been renamed since they were initially introduced. I recommend testing support for @position-try using anchor-name or position-try as telltale properties.

@supports (position-try: flip-block) {
  /* @position-try is supported! */
}

@scope (partial support)

The @scope at-rule seems tricky to test at first, but it turns out can apply the same strategy we did with style queries. Create a base style for browsers that don’t support @scope and then override those styles inside a @scope block that will only be valid in supporting browsers. A progressive enhancement strategy if there ever was one!

.foo .element {
  /* Base style */
}

@scope (.foo) to (.bar) {
  :scope .element {
    /* @scope is supported, override base style */
  }
}

@view-transition (partial support)

The last at-rule in this list is @view-transition. It’s another feature making quick strides into browser implementations, but it’s still a little ways out from being considered baseline support.

The easiest way would be to use its related view-transition-name property since they released close together:

@supports (view-transition-name: custom-ident) {
  /* @view-transition is supported! */
}

But we may as well use the selector() function to check for one of its many pseudo-elements support:

@supports selector(::view-transition-group(transition-name)) {
  /* @view-transition is supported! */
}

A little resource

I put this list into a demo that uses @supports to style different at-rules based on the test recipes we covered:

CodePen Embed Fallback

The unsolved ones

Even though I feel like I put a solid list together, there are three at-rules that I couldn’t figure out how to test: @layer, @property, and @starting-style.

Thankfully, each one is pretty decently supported in modern browsers. But that doesn’t mean we shouldn’t test for support. My hunch is that we can text @layer support similar to the approaches for testing support for style() queries with @container where we set a base style and use progressive enhancement where there’s support.

The other two? I have no idea. But please do let me know how you’re checking support for @property and @starting-style — or how you’re checking support for any other feature differently than what I have here. This is a tricky puzzle!


Recipes for Detecting Support for CSS At-Rules

Scroll to Top