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-alternates
l, 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:
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!