As mentioned in my gist about a CSS switch-like function,
authors would like to have a mechanism for less verbose CSS @media
rules, as expressed in
CSSWG's issue 5009.
In that issue a possible new syntax (with a few variants) is suggested, with authors specifying stacked property values that are chosen when they match a media feature value from a given set.
Although no detailed specification proposals were initially given (it all started from a tweet), most of the suggested syntaxes
involved widths or lengths (range features), and in those cases one either assumes an ordered set of feature values (with the
opposite behavior being required for e.g. max-width
and min-width
, which is problematic) or uses some kind of comparison
operators (like other proposals posted later in that thread did).
This proposal presents a mechanism of a similar kind, but more general and more formally specified. It is not based on the usage of comparison operators, although the media features themselves may contain them.
Although the level of detail of some parts of the specification may seem a bit of overkill, that is necessary to offer a consistent solution with general validity.
The basic idea is to assign a value to a custom property, based on the medium matching one or more of a list of media queries. If more than one media query (MQ) is matched, then a new concept of media query specificity (defined later) is introduced so the value corresponding to the most specific, matching MQ is used. If the medium matches more than one of those 'most-specific' MQs, then the last one is used (as typical in CSS).
The syntax has semicolon separators, like in one of the variants for my switch-like proposal:
@assign <custom-property-name> (<declaration-value> [; <declaration-value>]{0,});
so comma-separated values can be assigned.
If the medium matches a most-specific MQ located at position N in the MQ list, then the <declaration-value>
at position N
is used to set the custom property. If N is greater than the number of values in the assign, the last value is used.
Example:
@assign --my-font-size (14pt; 20pt; 25pt);
@assign --my-padding (10px; 20px; 50px);
Depending on where one puts those (potentially small) rules, regular properties could be assigned as well, although that would be a huge change in how regular properties are set.
So where do we put these rules? this proposal includes two initial variants for it.
The most obvious path would be to use plain @media
rules, for example:
@media (width >= 1200px), (700px <= width < 1200px), (width < 700px) {
:root {
@assign --my-font-size (25pt; 20pt; 14pt);
@assign --my-padding (50px; 20px; 10px);
}
}
or
@media (max-width: 700px), (min-width: 700px), (min-width: 1200px) {
:root {
@assign --my-font-size (14pt; 20pt; 25pt);
@assign --my-padding (10px; 20px; 50px);
}
}
and seeing that last example, one may think: "but a display may match more than one of those media queries". And here is where the concept of specificity is used. A device with a 1500px viewport width matches the last two, but the last is more specific. And a 700px-wide viewport matches the first two (which have the same specificity) so the second one wins.
Using @media
rules allows to reuse an existing syntax but has a potential pitfall: if somebody decides to edit the media query
list without being aware of the embedded @assign
rules, they may break the style sheet. The second variant is less powerful
but avoids that problem.
Instead of using media rules, this variant introduces a new rule that has only one job: to enable @assign
rules.
The syntax:
@select <media-query-list> {
<assign-list>
}
where <assign-list>
is obviously a list of @assign
rules. Notice that there are no selectors in that rule, because all
properties are assigned to :root
; otherwise it would be confusingly similar to @media
.
So far, different syntaxes have been defined but an important part is missing: how to establish the specificity of media
queries. And the first thing that we need to consider is that in a media query list like those shown above, one shall typically
find MQs that apply different tests over the same media feature (width
, for example). But they could also contain arbitrarily
different features (unlikely, but needs to be specified).
First, let's define the specificity of MQs that test the same media feature, and then we shall define it for combinations of different features. In our context, the specificity is based on the concept that a MQ may be contained by another MQ, under the following definition:
If query A contains query B, then if a medium matches B it will also match A.
Naturally, the opposite may not be true (unless A is equal to B).
More definitions:
- For range features, if query A contains query B, then B is more specific than A.
- For discrete features, if query A contains query B, then A is more specific than B.
- If A does not contain B, nor B contains A (for example
(max-width: 700px)
and(min-width: 700px)
), they have the same specificity.
Some clarifications:
- If A contains B but B happens to contain A as well, then they have the same specificity (because A is equal to B).
- If a MQ matches a feature through one or more of the items in a
OR
grouping, then only the most specific matching item in theOR
is considered.
Examples:
(height > 500px)
contains(720px < height <= 1080px)
. The latter is more specific.(color-gamut: rec2020)
contains(color-gamut: srgb)
. The former is more specific.
Typically, values of discrete features have all the same specificity (with the exception of color-gamut
) because one value
generally does not contain the others.
In most cases the media query lists are only going to test a single feature, but the case of several different features being
checked through a AND
grouping needs to be specified. For example, (height > 500px) and (monochrome)
is more specific than
(height > 600px)
but let's see how:
a) Some features have higher precedence than others, and a medium matching a feature with a higher precedence will always be
more specific. That is, if a media query A matches a feature with higher precedence than (also matching) query B, A will
always be more specific than B, even if B matches multiple other lower-precedence features through a AND
grouping.
b) When a media query matches two or more features (via AND
), it is more specific than a query that matches less features
with the same precedence and specificity.
c) When query A matches two or more features of the same precedence (via AND
) and one or more of those features is more
specific in B, then B is more specific, regardless of the number of features being tested in A.
The following media features are ordered by precedence, higher first:
forced-colors
ifactive
, andprefers-contrast
when not evaluating tono-preference
. Alsogrid
, when evaluating to true.monochrome
, when evaluating to true.width
,height
andaspect-ratio
.inverted-colors
.dynamic-range
,color-gamut
andcolor
.resolution
.- Other features: they share the same lowest precedence.
Examples:
(forced-colors: active)
is more specific than(monochrome)
, due to a).(800px < width < 1200px) and (monochrome)
is more specific than(width > 500px) and (monochrome)
by virtue of c).(width > 500px) and (aspect-ratio > 4/3) and (monochrome)
is more specific than(width > 500px) and (monochrome)
because of b).(width > 500px) and (aspect-ratio > 4/3) and (monochrome)
is less specific than(500px < width < 1000px) and (monochrome)
because of c).
However, and as mentioned before, this feature precedence mechanism is unlikely to be extensively used, as authors are probably going to avoid the mix of different media features.
The implementation of media query specificity, albeit being based on concepts that are relatively simple, shall require a fair amount of coding and testing. To estimate that effort, it may be of help to look into an implementation that has done part of the work. My css4j library implements (in the Java language) the MediaQueryList.matches(MediaQueryList) method, which basically tests the 'contains' relationship defined above.
A non-exhaustive count of the lines of code involved with the functionality, across some of the relevant source files, yields a number around 1000 (not including tests, which add several times that figure). Not tremendous but not trivial either, with C++ implementations likely to be more verbose. And that's only part of the code that would have to be written, as then one has to implement the specificity on top of the 'contains', add support for the new syntax constructs, the value assignment...
The mechanism described in this document does the job for which it was devised, but the implementation costs would be relatively expensive.
This proposal would just allow a more compact form of specifying media-dependent properties, not introducing anything that could not be done previously. Therefore, it has no specific security implications.
Specifying this functionality requires a bit of complexity, but most authors should be able to use it nearly instinctively, without having to learn all the details. However, my opinion is that my other proposal about the switch-like function is simpler to implement and easy to understand by authors, looking like the simplest path to address their needs.
On the other hand, the proposal described in this document seems especially well suited for frequent use cases and, if that kind of approach is ever to be followed, provides a consistent framework that could be used.