|
| 1 | +# At-Rule Feature Detection |
| 2 | + |
| 3 | +## Authors: |
| 4 | + |
| 5 | +- [Kevin Babbitt](https://github.com/kbabbitt) (Microsoft) |
| 6 | + |
| 7 | +## Participate |
| 8 | +- [Issue tracker](https://github.com/MicrosoftEdge/MSEdgeExplainers/labels/AtRuleFeatureDetection) |
| 9 | + |
| 10 | +## Table of Contents |
| 11 | + |
| 12 | +<!-- START doctoc generated TOC please keep comment here to allow auto update --> |
| 13 | +<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE --> |
| 14 | + |
| 15 | + |
| 16 | +- [At-Rule Feature Detection](#at-rule-feature-detection) |
| 17 | + - [Authors:](#authors) |
| 18 | + - [Participate](#participate) |
| 19 | + - [Table of Contents](#table-of-contents) |
| 20 | + - [Introduction](#introduction) |
| 21 | + - [User-Facing Problem](#user-facing-problem) |
| 22 | + - [Goals](#goals) |
| 23 | + - [Non-goals](#non-goals) |
| 24 | + - [Proposed Approach](#proposed-approach) |
| 25 | + - [Detect whether an at-rule name is recognized at all](#detect-whether-an-at-rule-name-is-recognized-at-all) |
| 26 | + - [Detect whether an at-rule, with optional prelude and/or block, is supported](#detect-whether-an-at-rule-with-optional-prelude-andor-block-is-supported) |
| 27 | + - [Special case: The forgiving grammar of media queries](#special-case-the-forgiving-grammar-of-media-queries) |
| 28 | + - [Detect whether a given declaration is supported within an at-rule block](#detect-whether-a-given-declaration-is-supported-within-an-at-rule-block) |
| 29 | + - [Accessibility, Privacy, and Security Considerations](#accessibility-privacy-and-security-considerations) |
| 30 | + - [Stakeholder Feedback / Opposition](#stakeholder-feedback--opposition) |
| 31 | + - [References \& acknowledgements](#references--acknowledgements) |
| 32 | + |
| 33 | +<!-- END doctoc generated TOC please keep comment here to allow auto update --> |
| 34 | + |
| 35 | +## Introduction |
| 36 | + |
| 37 | +Feature detection is a [W3C TAG design principle](https://www.w3.org/TR/design-principles/#feature-detect) |
| 38 | +and a tool that Web authors rely on for graceful degradation of their pages. |
| 39 | + |
| 40 | +[CSS Conditional Rules](https://www.w3.org/TR/css-conditional/) introduces the `@supports` rule and API |
| 41 | +extensions to allow authors to feature-detect CSS properties. |
| 42 | +In this explainer, we describe an expansion to feature detection in CSS that allows authors to detect |
| 43 | +support for at-rules, including specific features of at-rules. |
| 44 | + |
| 45 | +## User-Facing Problem |
| 46 | + |
| 47 | +There have been many scenarios described that call for feature detection of at-rules and sub-portions of at-rule grammar. Some examples: |
| 48 | + |
| 49 | +- In the [Blink intent-to-ship thread for `@property`](https://groups.google.com/a/chromium.org/g/blink-dev/c/3ygpsew53a0/m/Ar_OPlthAwAJ), it was pointed out that authors need a mechanism to detect support so that they can fall back to `CSS.registerProperty()` if needed. |
| 50 | +- A [StackOverflow question](https://stackoverflow.com/questions/44244221/is-it-possible-to-do-a-css-supports-check-on-a-media-rule) asks whether it is possible to detect support for `@media` features, for example to detect if the user agent can return a yes/no answer for `@media (pointer)`. |
| 51 | +- A [Mastodon post](https://mastodon.social/@xro/113106213499516093) asks whether it is possible to test for style query support. |
| 52 | +- At time of writing, several in-development CSS features propose to implement new at-rules. These include [`@sheet`](https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/AtSheet/explainer.md) as well as [CSS Functions and Mixins](https://css.oddbird.net/sasslike/mixins-functions/). |
| 53 | + |
| 54 | +### Goals |
| 55 | + |
| 56 | +Allow authors to feature-detect newly introduced at-rules. |
| 57 | + |
| 58 | +Allow authors to feature-detect new enhancements to existing at-rules, such as: |
| 59 | +- New media query features and other additions to at-rule preludes |
| 60 | +- New descriptors that may be introduced to rules such as `@font-face` |
| 61 | + |
| 62 | +At-rule feature detection should be available in all contexts where CSS allows conditioning based on support |
| 63 | +of a feature. This includes, but is not limited to, |
| 64 | +`@supports`, `CSS.supports()`, `@import ... supports()`, and `@when supports()`. |
| 65 | + |
| 66 | +### Non-goals |
| 67 | + |
| 68 | +The CSS `@charset` rule, despite its appearance, is |
| 69 | +[not an at-rule](https://drafts.csswg.org/css-syntax/#charset-rule). |
| 70 | +Rather, `@charset` is a marker that can appear only as the first few bytes of a stylesheet file. It signals to |
| 71 | +the user agent what character encoding should be used to decode the contents of the stylesheet. |
| 72 | + |
| 73 | +CSS feature detection has, since its inception, relied on the test of "does it parse successfully?" to |
| 74 | +answer the question of whether a property-value pair is supported. Because of its special nature, user agents |
| 75 | +may have a different parsing implementation for `@charset` compared to true at-rules, which might not be as easily |
| 76 | +reused for feature detection. |
| 77 | + |
| 78 | +At the same time, there is far less of a use case for feature-detecting `@charset` compared to true at-rules. |
| 79 | +`@charset` is part of the [Baseline](https://developer.mozilla.org/en-US/docs/Web/CSS/@charset) feature set long |
| 80 | +supported in all major browsers. On the modern Web, the encouraged solution for character encoding issues in |
| 81 | +CSS is to use UTF-8. |
| 82 | + |
| 83 | +Accordingly, this explainer does not propose making `@charset` feature-detectable using `at-rule()`. |
| 84 | + |
| 85 | +## Proposed Approach |
| 86 | + |
| 87 | +The `at-rule()` function can be used for feature detection in the following ways: |
| 88 | + |
| 89 | +### Detect whether an at-rule name is recognized at all |
| 90 | + |
| 91 | +In its simplest form, the `at-rule()` function can be passed just an at-rule name. |
| 92 | +The result is true if the implementation would recognize it as an at-rule in any context, false otherwise. |
| 93 | +This form is useful for detecting entire new features implemented as at-rules, including features such as |
| 94 | +[`@starting-style`](https://www.w3.org/TR/css-transitions-2/#defining-before-change-style) |
| 95 | +that do not appear at top-level stylesheet context. |
| 96 | + |
| 97 | +```css |
| 98 | +/* Use reusable styles encapsulated in @sheet if supported. */ |
| 99 | +@import "reusable-styles.css" supports(at-rule(@sheet)); |
| 100 | + |
| 101 | +/* Fall back to tooling-expanded styles if not. */ |
| 102 | +@import "expanded-styles.css" supports(not(at-rule(@sheet))); |
| 103 | + |
| 104 | +/* ... */ |
| 105 | + |
| 106 | +/* Set up a pop-in transition with @starting-style if it's supported. */ |
| 107 | +@supports at-rule(@starting-style) { |
| 108 | + .card { |
| 109 | + transition-property: opacity, transform; |
| 110 | + transition-duration: 0.5s; |
| 111 | + @starting-style { |
| 112 | + opacity: 0; |
| 113 | + transform: scale(0); |
| 114 | + } |
| 115 | + } |
| 116 | +} |
| 117 | +``` |
| 118 | + |
| 119 | +It may also be useful as a shorter alternative to the second form for feature-detecting at-rules that are only |
| 120 | +valid when nested inside another at-rule, such as |
| 121 | +[`@swash`](https://www.w3.org/TR/css-fonts/#font-feature-values-syntax) |
| 122 | +and other font feature value types within `@font-feature-values`. |
| 123 | + |
| 124 | +```css |
| 125 | +@supports at-rule(@swash) { |
| 126 | + @font-feature-values Foo { |
| 127 | + @swash { pretty: 1; cool: 2; } |
| 128 | + } |
| 129 | + p { |
| 130 | + font-family: Foo; |
| 131 | + font-variant-alternates: swash(cool); |
| 132 | + } |
| 133 | +} |
| 134 | +@supports not(at-rule(@swash)) { |
| 135 | + /* Fall back to something else. */ |
| 136 | + @font-face FooButNotAsCool { |
| 137 | + /* ... */ |
| 138 | + } |
| 139 | + p { |
| 140 | + font-family: FooButNotAsCool; |
| 141 | + } |
| 142 | +} |
| 143 | +``` |
| 144 | + |
| 145 | +However, authors should consider the possibility of such an at-rule later becoming valid in a new and different |
| 146 | +context, which may result in a false positive. For example, one might write `@supports at-rule(@top-left)` |
| 147 | +intending to detect support for the `@top-left` rule nested within `@page`. But later, if a new feature comes |
| 148 | +along that implements a nested `@top-left` at-rule for a different purpose, the feature query would return true |
| 149 | +on implementations that *do* support this new feature but *do not* support `@page`. |
| 150 | + |
| 151 | +### Detect whether an at-rule, with optional prelude and/or block, is supported |
| 152 | + |
| 153 | +This form resembles existing use of feature detection in CSS. An at-rule block, including optional prelude |
| 154 | +and/or declaration block, is passed as the function parameter. If the at-rule block is accepted by the |
| 155 | +implementation *without relying on |
| 156 | +[forgiving catch-all grammar](#special-case-the-forgiving-grammar-of-media-queries)*, |
| 157 | +the support query returns true; otherwise it returns false. |
| 158 | + |
| 159 | +This is useful for detecting new enhancements to existing at-rules. Often it is not necessary to pass the entire |
| 160 | +at-rule that the author intends to use; an abbreviated subset can be "close enough" to make the right decision. |
| 161 | + |
| 162 | +```css |
| 163 | +/* Are style queries supported at all? */ |
| 164 | +@supports at-rule(@container style(color: green)) { |
| 165 | + /* Yes - use them to emphasize green cards. */ |
| 166 | + @container style(color: green) and not style(background-color: red) { |
| 167 | + .card { |
| 168 | + font-weight: bold; |
| 169 | + transform: scale(2); |
| 170 | + } |
| 171 | + } |
| 172 | +} |
| 173 | +``` |
| 174 | + |
| 175 | +The parsing test is performed as if the given at-rule block were the first and only content in a stylesheet. |
| 176 | +That allows for at-rules with positional requirements, such as `@import`, to be tested: |
| 177 | + |
| 178 | +```css |
| 179 | +/* Import styles into a named layer. */ |
| 180 | +/* If the implementation does not support layer(), this at-rule will be ignored. */ |
| 181 | +@import "layered-styles.css" layer(my-layer); |
| 182 | + |
| 183 | +/* If the implementation does not support layers, fall back to unlayered styles. */ |
| 184 | +@import "unlayered-styles.css" supports(not(at-rule(@import "test.css" layer(test)))); |
| 185 | +``` |
| 186 | + |
| 187 | +#### Special case: The forgiving grammar of media queries |
| 188 | + |
| 189 | +"Forgiving catch-all grammar" refers to cases where it would be undesirable for an entire at-rule to be |
| 190 | +thrown away just because a small part of it is unrecognized. One example is in media queries: |
| 191 | + |
| 192 | +```css |
| 193 | +@media (max-width: 800px and not(fancy-display)) { |
| 194 | + /* ... */ |
| 195 | +} |
| 196 | +``` |
| 197 | + |
| 198 | +The implementation does not recognize the `fancy-display` media feature. But since the author is testing that |
| 199 | +the page is *not* on a fancy display, we still want the rules within the `@media` block to apply. The media query |
| 200 | +handles this by returning an "unknown" value for `fancy-display`, which gets treated as "false" for the purpose |
| 201 | +of evaluating the query. |
| 202 | + |
| 203 | +Suppose an author instead wants to condition part of their stylesheet on whether an implementation recognizes |
| 204 | +`fancy-display` *at all*. Implementations that recognize `fancy-display`, and implementations that don't, both |
| 205 | +must at least parse the above media query. If feature detection were determined purely based on whether the |
| 206 | +at-rule parses successfully, it would not be possible to feature-detect support for `fancy-display`. The |
| 207 | +exception we carve out allows us to handle this situation: Implementations that recognize `fancy-display` will |
| 208 | +parse that feature name on their "known media features" path, and implementations that don't recognize it will |
| 209 | +parse it on their "forgiving catch-all" path. |
| 210 | + |
| 211 | +```css |
| 212 | +/* This @supports query returns true if the implementation knows what a fancy-display is. */ |
| 213 | +@supports at-rule(@media (fancy-display)) { |
| 214 | + |
| 215 | + /* This @media query returns true if the user is actually using a fancy-display. */ |
| 216 | + @media (max-width: 800px and fancy-display) { |
| 217 | + /* ... */ |
| 218 | + } |
| 219 | + |
| 220 | + /* This @media query returns true if the user is detectably not using a fancy-display. */ |
| 221 | + @media (max-width: 800px and not(fancy-display)) { |
| 222 | + /* ... */ |
| 223 | + } |
| 224 | +} |
| 225 | +``` |
| 226 | + |
| 227 | +### Detect whether a given declaration is supported within an at-rule block |
| 228 | + |
| 229 | +This form allows testing support for descriptors or property declarations within a given at-rule. |
| 230 | +Given an at-rule name and a declaration, the support query returns true if the declaration parses within |
| 231 | +the at-rule's block, false otherwise. |
| 232 | + |
| 233 | +```css |
| 234 | +@supports at-rule(@property; syntax: "<color>") { |
| 235 | + /* declare custom properties with color syntax */ |
| 236 | +} |
| 237 | + |
| 238 | +@supports at-rule(@position-try; box-shadow: 0px 0px) { |
| 239 | + /* declare rules that set different box-shadow locations for different position-areas */ |
| 240 | +} |
| 241 | +``` |
| 242 | + |
| 243 | +Such tests could also be accomplished with the previous form, but this form allows authors to omit the prelude |
| 244 | +and/or required declarations that are not the subject of the test. |
| 245 | +For example, the `@font-face` rule requires `font-family` and `src` descriptors. |
| 246 | +An author who wants to feature detect support for `font-feature-settings` using the previous form would need |
| 247 | +to supply dummy values for `font-family` and `src` in order for the test block to parse successfully. |
| 248 | +The query would thus be quite verbose: |
| 249 | + |
| 250 | +```css |
| 251 | +/* Testing as a full at-rule using the previous form */ |
| 252 | +@supports at-rule(@font-face {font-family: test; src: local(test); font-feature-settings: "hwid"} ) { |
| 253 | + /* ... */ |
| 254 | +} |
| 255 | +``` |
| 256 | + |
| 257 | +With this form, the author can simplify to: |
| 258 | + |
| 259 | +```css |
| 260 | +/* Simpler query using this form */ |
| 261 | +@supports at-rule(@font-face; font-feature-settings: "hwid") { |
| 262 | + /* ... */ |
| 263 | +} |
| 264 | +``` |
| 265 | + |
| 266 | +## Accessibility, Privacy, and Security Considerations |
| 267 | + |
| 268 | +No accessibility, privacy, or security considerations have been identified for this feature. |
| 269 | + |
| 270 | +## Stakeholder Feedback / Opposition |
| 271 | + |
| 272 | +Feedback from other implementors will be collected as part of the Blink launch process. |
| 273 | + |
| 274 | +## References & acknowledgements |
| 275 | + |
| 276 | +This explainer describes a feature which others have already put significant work into. |
| 277 | +Many thanks for the efforts of: |
| 278 | + |
| 279 | +- Fuqiao Xue, who brought the original feature request to the CSSWG in Issue [#2463](https://github.com/w3c/csswg-drafts/issues/2463). |
| 280 | +- Tab Atkins-Bittner, who proposed expansion of the grammar in Issue [#6966](https://github.com/w3c/csswg-drafts/issues/6966). |
| 281 | +- Steinar H. Gunderson, who implemented the behavior that the CSSWG resolved on in [#2463](https://github.com/w3c/csswg-drafts/issues/2463) in Chromium behind a feature flag. |
| 282 | +- Anders Hartvoll Ruud, who raised important clarifying questions in Issues [#11116](https://github.com/w3c/csswg-drafts/issues/11116), [#11117](https://github.com/w3c/csswg-drafts/issues/11117), and [#11118](https://github.com/w3c/csswg-drafts/issues/11118). |
0 commit comments