Repository navigation
[css-conditional] @supports-condition, for larger feature queries and named reuse #12622
Description
Originally, @supports only took a nice (property: value) query that looked exactly like the declaration being queried. Over time, we started expanding it to a parallel CSS meta-syntax, with selector() and now at-rule() referring to at-rules separate from their actual syntax, creating issues such as #6966 or #11118.
This got me thinking: it would be nice if authors could basically stuff the thing they want to detect somewhere without having to know or care what type of CSS syntax it is and how to query support in @supports.
What if we had an at-rule that could be used to store and reference conditions, and can accept any parse-able CSS syntax? It can then be used naked in @supports and resolves to true if the rules nested within the @supports-condition rule do not produce any parse errors.
Direct manipulation over indirection.
CSS grammar is already defined to have robust-enough parsing that it can handle parsing unknown structures, so this should be doable syntactically.
Proposal: Named feature queries
It would be defined as a nested at-rule, that can accept declarations or rules, so it would work like this:
Raw declaration:
@supports-condition --masonry-h {
display: grid-stack;
item-direction: row;
}
/* Equivalent to (display: grid-stack) and (item-direction: row) */
@supports --masonry-h {
/* stuff */
}More natural syntax for selector():
@supports-condition --part-v2 {
&::part(foo):hover,
&::part(bar)::before:hover {
}
}
/* Like @supports selector(&::part(foo):hover, &::part(bar)::before:hover) */
@supports --part-v2 {
/* stuff */
}More natural syntax for at-rule() (addressing #11118)
@supports-condition --page-margin {
@page {
margin: 0;
@top-right {
margin: 0;
}
}
}
@supports --page-margin {
/* ... */
}This also means that we can instantly test the entirety of CSS, with dedicated @supports functions needed only for things that do parse correctly.
And this feeds two birds with one scone: it solves the problem of "how do we use @supports to query XYZ?" and also provides a reuse mechanism.
It almost seems too good to be true, so I wonder if it's feasible implementation-wise to get parse error info for a larger structure like this as a whole. Initial discussion with @tabatkins was promising though!
Named inline feature queries
This can do anything that selector() can, but selector() can still be useful as a shorthand (and we need to keep it for compat).
I think if this direction goes forwards, it could eliminate at-rule() since it hasn't shipped yet and involves a lot of unsolved issues and syntactic awkwardness. Or we could greatly simplify it and offer it only as a shortcut for making simple cases easy (e.g. drop the descriptor argument).
Dedicated functions are still necessary for things that do parse correctly. For example we can't replace font-tech() with
@supports --font-tech-color-COLRv1 {
@font-face {
font-family: foo;
src: url(foo.ttf) tech(color-COLRv1);
}
}because this would parse anyway.
To allow reuse for such conditions as well the rule could have a no-contents version:
@supports-condition --font-tech-foo font-tech(foo);
@supports --font-tech-foo {
/* ... */
}TBD: Combining named feature queries with inline feature queries
For this to also function as a reuse mechanism, we need a way to build more complex conditions from existing conditions. Bare identifiers cannot be combined with other conditions per the <condition> grammar, so we'd need to wrap it in parentheses for that.
This could either take the form of a bare parentheses syntax (@supports (--my-condition) and (foo: bar)) but that means we can never use (foo) to mean "does this property exist?", which is inconsistent with how style() conditions work.
Or, we could have a function, e.g. condition(--my-condition).
There is also the question of whether the parenthesized syntax is always required or only when combining with other conditions. I'm of the opinion that it should only be required when needed (incremental value for incremental user effort) but Tab feels this opens it up to potential errors. I would counter that in other expression syntaxes, we typically only parenthesize to combine with other terms, no defensively. But I can live with either.
Metadata
Metadata
Assignees
Type
Projects
- StatusShow more project fieldsWednesday Morning
- StatusShow more project fieldsAgenda+
Activity
[-][css-conditional] `@supports-condition`, for larger syntax and reuse[/-][+][css-conditional] `@supports-condition`, for larger syntax queries and query reuse[/+][-][css-conditional] `@supports-condition`, for larger syntax queries and query reuse[/-][+][css-conditional] `@supports-condition`, for larger feature queries and named reuse[/+]tabatkins commentedon Aug 19, 2025
We super intentionally didn't allow that syntax in
@supportsfrom the beginning, so I'm fine with continuing to deny that as a possibility.SebastianZ commentedon Aug 19, 2025
I really love this proposal! This approach catches all the cases
at-rule()falls short of like testing for preludes and support for whole at-rules.We just need to make sure that the spec. clearly states that anything inside of an
@supports-conditionis really just parsed to avoid any side effects. And we should make sure to allow at-rules to be tested that are currently only allowed at the top level.Sebastian
Crissov commentedon Aug 19, 2025
I think I like it, too, but for the “More natural syntax for
selector()”, I’m afraid an empty rule block might be optimized away together with its selectors, so I’d suggest to put something inside.AtkinsSJ commentedon Aug 19, 2025
This seems like a very neat solution to multiple problems! My initial concern was how to disambiguate the parsing between declarations and style rules, but I think that's already solved because otherwise nesting wouldn't work. Though... if you wanted to test both a declaration and something else in the same
@supports-condition, it gets a bit weird that declarations can be raw like that. eg, what context are we parsing things in here?Declarations could always be tested with
* { /* declarations here */ }so maybe the raw-declarations thing isn't necessary? It's nicer to author but just makes things a bit more confusing.I don't think that's an issue. An empty style rule can't be optimized away because it's observable through the CSSOM. (Also, inside a supports query you're just testing if something can parse, not that it would exist and be useful, so there shouldn't be any such optimizations done.)
33 remaining items
cdoublev commentedon Nov 4, 2025
What you resolved on does not seem very explicit. 😟
Given the CSS below, what should be the computed
colorofdivwhen the media width is greater than500pxat parse time? and if it becomes lower than500px?(edit) According to the discussions in #12627, it would be red, and would remain red regardless of how the media condition evaluates.
(edit) wrapped --name in () block as required by the syntax, see next comment.
In any case, I expect that
@supports-conditionwith the same name will not be dropped, like other at-rules registering named things.It would be great to clarify this:
Should these values evaluate to true?
style { /* comment */ }style { oups; }style { @media --oups {} }style { @media (oups) {} }style { color: green; color: green; }bramus commentedon Nov 4, 2025
Isn’t that detailed in #12536 (comment)?
Sidenote: Having been AFK for a week and now seeing
@supports --name { … }it took me a while to decipher. I first read it as “Is the dashed ident --name” supported, whereas it’s “Is the custom condition--namesupported”? Maybe we need something extra around--namein@supports --name { … }? Cfr. what @dbaron said before.cdoublev commentedon Nov 4, 2025
Thanks, I had not scrolled down. ^^
But this does not tell whether a nested condition is "available" when its parent condition evaluates to true.
(edit) According to the discussions in #12627, the parent condition is ignored so the nested named support condition is "available".
The syntax currently requires a () block.
SebastianZ commentedon Nov 5, 2025
@bramus @cdoublev:
To me and obviously also @sorvell, that explanation was still somewhat vague. Therefore, I've expressed my interpretation in the other issue.
@emilio:
I agree that we should do the same thing as for custom media queries.
@cdoublev:
True, some more detailed explanation and examples may help here. My expectation is:
It's valid even when there are no properties defined, so yes. And there is already an example related to nesting in the spec.
oups;is not a valid declaration, so no. The spec should be clarified to say that everything nested inside@supports-conditionand must be recognized by the UA.As it stands here,
--oupswould be interpreted as a media type, which doesn't exist, so no.If it were wrapped in parentheses, it'd be intrepted as custom media query. In that case, I'd argue we should apply the rules resolved on in #12536. So I'd say, only if the custom media query is defined at that point, it should evaluate to true.
oupsis not a recognized media feature, so no.No issue with repetition, so yes.
Sebastian
cdoublev commentedon Nov 5, 2025
Based on your answers, it must evaluate to false whenever consume a block's contents get nothing from consuming a rule or a declaration, and forgiving parsing must be disabled.
I have not included examples with
@charset,@import,@namespace.A different but related problem is that
@supports-conditionmay be restricted to nested style/grouped rules when nested in a style rule, according to CSS Nesting.But these are minor problems that can be fixed later and discussed in a separate issue, I guess.
SebastianZ commentedon Nov 6, 2025
All good points! While I initially specced
@supports-condition, it was @LeaVerou's suggestion and she got added as editor to the spec. So I refer to her to further adjust the definition.🙃Sebastian
cdoublev commentedon Nov 6, 2025
After rereading previous discussions more carefully, it might not be a problem but rather intentional:
@supports-conditionblock value should accept any valid content in the context, if I am not mistaken. (The spec currently includes an example with top-level property declarations.)Other questions/concerns:
@supports-conditionbe parsed like a style sheet or a block contents? (they have different content end tokens)@supports-conditioncontents viaCSSSupportsFunctionRule?CSSSupportsFunctionRuledoes not extendCSSConditionRulewhile@supports-conditionis defined as a conditional group rule?CSS.supports(--name)?Rescopes at-rule down to test to just `at-rule(@foo)`
Re-scopes at-rule (#1231)
css-meeting-bot commentedon Mar 25, 2026
The CSS Working Group just discussed
[css-conditional] `@supports-condition`, for larger feature queries and named reuse, and agreed to the following:RESOLVED: Use @supports (--foo) for custom supports conditions.The full IRC log of that discussion
<fantasai> lea: Specific grammar questions from @cdoublev<TabAtkins> here we go https://github.com//issues/12622#issuecomment-3435923624
<fantasai> lea: Sebastian added to the discussion here...
<fantasai> lea: Ideally Sebastian presents this?
<fantasai> TabAtkins: I think I can present this one now.
<fantasai> TabAtkins: Like Lea said, question is how do you refer to a custom supports condition in an @supports rule?
<fantasai> TabAtkins: 2 possibilities
<TabAtkins> whether it's (--foo)
<fantasai> TabAtkins: 1. Parenthesize it, but use dashed-ident.
<TabAtkins> condition(--foo)
<fantasai> TabAtkins: 2. Define a new query function, which takes the name of the condition.
<lea> q+
<Rossen> style {
<Rossen> @supports-condition --name { @font-face {} }
<Rossen> @supports (--name) {} /* evaluates to false even if @font-face is supported */
<Rossen> }
<fantasai> TabAtkins: I think that example is asking questions about nesting.
<fantasai> TabAtkins: Question is about how to refer to it in the supports rule.
<fantasai> TabAtkins: Concern with taking the name directly in parens is that it will prevent us from using that exact syntax for testing whether a property name is supported at all
<fantasai> TabAtkins: Other syntax we have is (property: values)
<fantasai> TabAtkins: We intentionally didn't add (property) to @supports so that we engage the CSS parser and get a true result
<fantasai> TabAtkins: Not a separately (and likely brokenly) maintained list in the browser
<fantasai> TabAtkins: So I think using bare name is fine here.
<fantasai> TabAtkins: Function is also fine with me, but more typing
<Rossen> ack lea
<fantasai> lea: Not a strong position either. Concerned about function vs custom media queries, which use bare idents
<fantasai> lea: Also question about nesting and how do multiple rules combine or what
<fantasai> lea: I cannot see a use case with wanting to interleave with regular elements, usually you define up front and then use
<fantasai> lea: I don't see what you might want to detect around bare idents
<fantasai> lea: Already a way to detect custom properties, though I can see why it feels too similar
<Rossen> ack fantasai
<TabAtkins> @media (--foo)
<fantasai> fantasai: Beign consistent with MQ makes sense to me.
<fantasai> s/Beign/Being/
<fantasai> dbaron: Fine with this, but if we take this syntax in @supports rules then we'll also take this anywhere a declaration can be used
<fantasai> dbaron: So as long as we're ok saying "we're not going to allow single custom keyword in places where you write property-value pairs" then this is fine. But that's a slightly bigger reservation of syntax than what some people are talkinga bout.
<Rossen> ack dbaron
<fantasai> TabAtkins: you mean inside a style rule?
<fantasai> dbaron: Yeah
<fantasai> TabAtkins: We are using at-rules for that, so I think it's OK.
<TabAtkins> fantasai: because we ahve custom idents for custom properties... and just dropping the value of the custom prop woudl be subtle for a new feature, it's probably okay to resolve that we won't do that
<TabAtkins> fantasai: and the consistency with MQs is a pretty strong argument for doing this, if we don't have strong reservations agaisnt it
<fantasai> PROPOSED: Use @supports (--foo) for custom supports conditions.
<fantasai> RESOLVED: Use @supports (--foo) for custom supports conditions.
<Rossen> ack fantasai
<fantasai> fantasai: Lea brought up questions about how to handle multiple rules with the same name, etc. Are we tackling those questions here also?
<fantasai> TabAtkins: Relying on last wins.