Skip to content

Commit b21c35e

Browse files
authored
Add explainer for at-rule feature detection (#1110)
1 parent 9475b72 commit b21c35e

File tree

3 files changed

+293
-0
lines changed

3 files changed

+293
-0
lines changed
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
name: At-Rule Feature Detection
3+
about: new issue
4+
title: "[At-Rule Feature Detection] <TITLE HERE>"
5+
labels: AtRuleFeatureDetection
6+
assignees: kbabbitt
7+
8+
---
9+
10+

AtRuleFeatureDetection/explainer.md

Lines changed: 282 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,282 @@
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).

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ we move them into the [Alumni section](#alumni-) below.
5656

5757
| Explainer | Issues | Feedback | Group |
5858
| --------- | ------ | -------- | -------- |
59+
| [At-Rule Feature Detection](AtRuleFeatureDetection/explainer.md) | <a href="https://github.com/MicrosoftEdge/MSEdgeExplainers/labels/AtRuleFeatureDetection">![GitHub issues by-label](https://img.shields.io/github/issues/MicrosoftEdge/MSEdgeExplainers/AtRuleFeatureDetection?label=issues)</a> | [New issue...](https://github.com/MicrosoftEdge/MSEdgeExplainers/issues/new?assignees=kbabbitt&labels=AtRuleFeatureDetection&template=at-rule-feature-detection.md&title=%5BAt-Rule+Feature+Detection%5D+%3CTITLE+HERE%3E) | CSS |
5960
| [CSS Gap Decorations](CSSGapDecorations/explainer.md) | <a href="https://github.com/MicrosoftEdge/MSEdgeExplainers/labels/CSSGapDecorations">![GitHub issues by-label](https://img.shields.io/github/issues/MicrosoftEdge/MSEdgeExplainers/CSSGapDecorations?label=issues)</a> | [New issue...](https://github.com/MicrosoftEdge/MSEdgeExplainers/issues/new?assignees=kbabbitt&labels=CSSGapDecorations&template=css-gap-decorations.md&title=%5BCSS+Gap+Decorations%5D+%3CTITLE+HERE%3E) | CSS |
6061
| [CSS ::tooltip Pseudo Element](CSSTooltipPseudo/explainer.md) | <a href="https://github.com/MicrosoftEdge/MSEdgeExplainers/labels/CSSTooltipPseudo">![GitHub issues by-label](https://img.shields.io/github/issues/MicrosoftEdge/MSEdgeExplainers/CSSTooltipPseudo?label=issues)</a> | [New issue...](https://github.com/MicrosoftEdge/MSEdgeExplainers/issues/new?template=tooltip-pseudo.md) | CSS |
6162
| [Enterprise Platform Authentication Broker API](PlatformAuthentication/explainer.md) | <a href="https://github.com/MicrosoftEdge/MSEdgeExplainers/labels/Enterprise%20Platform%20Authentication">![GitHub issues by-label](https://img.shields.io/github/issues/MicrosoftEdge/MSEdgeExplainers/PlatformAuthentication?label=issues)</a> | [New issue...](https://github.com/MicrosoftEdge/MSEdgeExplainers/issues/new?assignees=coder-linping&labels=Enterprise%20Platform%20Authentication&template=platform-authentication-api.md&title=%5BPlatform+Authentication+API%5D+%3CTITLE+HERE%3E) | Identity |

0 commit comments

Comments
 (0)