A proposal to improve the handling of list-value and set-valued properties when one wants to alter them in multiple independent ways, without overriding each other or having to explicitly handle all combinations.
CSS is a language for describing the rendering of structured documents
(such as HTML and XML)
on screen, on paper, in speech, etc.
Status of this document
1. Introduction
A number of CSS properties are list-valued (background, transition, etc) or set-valued (will-change, etc).
These can be difficult to manage in an application written by several authors
(or partially styled by indepedent libraries),
or even when there’s only a single author
that just wants to style an element in multiple independent ways.
For example,
a particular class might do some transform work,
and thus want to set will-change: transform,
while another class does some opacity changes,
and thus wants to set will-change: opacity.
Done naively, if these two classes get set at the same time,
one will win over the other,
and will-change will only reflect one of the values.
Currently, the only way to get around this is to explicitly handle the collision,
manually applying will-change: transform, opacity when the element matches both classes.
This sort of explicit collision-handling is unmaintainable,
and can quickly grow out of control if more cases are added,
as each one increases the number of combinations to be handled combinatorially.
(A third class means you need to explicitly handle the 1+2, 1+3, 2+3, and 1+2+3 cases.
A fourth class means handling 11 different combinations!)
In this spec we propose a few possible mechanisms to handle these situations more elegantly.
2. Cascade Declarations
During the cascade process,
multiple declarations of the same property on a given element
are sorted in specificity order
to produce the output of the cascade,
and then only the last value
(the one with highest specificity)
is actually used as the value of that property on the element.
A cascade modifier is a modifier on a property name
that makes the property’s value depend partially on the previous cascaded value:
the value for that property that comes before the current value
(that is, is lower specificity)
in the output of the cascade.
This involves defining the "unit" of each set-valued property
(for example, in will-change each keyword is a "unit"),
and ensuring that all of them have a "null" value
(like none).
The "unit" of list-valued properties are much easier to define;
for all but some legacy properties like counter-reset,
it’s just splitting on commas.
Define that, for all of these,
we interpret the property as normal first,
then split it into units for merging;
this isn’t variable-style concatenation.
That is, the following does not define a 2px-offset blue shadow:
As each property is still interpreted as a property,
the first is interpreted the same as box-shadow: 2px 2px; (specifying a single drop-shadow set to currentcolor),
while the second is simply invalid.
2.1. Managing Order Explicitly with Variables
Using cascade modifiers directly can achieve a number of useful, simple effects,
but direct usage doesn’t allow a number of common use-cases.
For example, you can’t override a particular cascade modifier with a more specific rule.
In other words, so long as the element matches the div.foo rule,
the value will contain url(one.jpg).
(You can override the entire thing with a higher-specificity declaration
that doesn’t use a cascade modifier,
but that overrides the base.jpg too.
There’s no way to directly override just one of the modified declarations.)
The preferred pattern to achieve this is to use a custom property:
To fix the previous example,
so base.jpg always applies
but one.jpg and two.jpg apply based on specificity,
you can write:
Why not just use variables by themselves? Why the extra complexity?
The above example could instead be written only using variables,
with no cascade controls:
div {background-image:var(--upper-background, none),url(base.jpg);}div.foo {--upper-background:url(one.jpg);}div.foo.bar {--upper-background:url(two.jpg);}
While this works in simple situations,
it’s less useful as more things interact.
It requires that background-image never be disturbed;
if anything else attempts to set background-image,
it’ll wipe out the variable use.
The cascade modifier approach,
on the other hand,
maintains the images set by the variables
even if other code sets the background-image property.
3. cascade() Function
More directly but slightly more complex,
we could add a cascade() function
that’s accepted by all set-valued and list-values properties
as a whole "unit".
By default it subs in the previous cascaded value for itself,
but for set-valued things it needs to offer more functionality to do difference/intersection.
Conformance
Document conventions
Conformance requirements are expressed with a combination of
descriptive assertions and RFC 2119 terminology. The key words “MUST”,
“MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”,
“RECOMMENDED”, “MAY”, and “OPTIONAL” in the normative parts of this
document are to be interpreted as described in RFC 2119.
However, for readability, these words do not appear in all uppercase
letters in this specification.
All of the text of this specification is normative except sections
explicitly marked as non-normative, examples, and notes. [RFC2119]
Examples in this specification are introduced with the words “for example”
or are set apart from the normative text with class="example",
like this:
This is an example of an informative example.
Informative notes begin with the word “Note” and are set apart from the
normative text with class="note", like this:
Note, this is an informative note.
Advisements are normative sections styled to evoke special attention and are
set apart from other normative text with <strong class="advisement">, like
this: UAs MUST provide an accessible alternative.
Conformance classes
Conformance to this specification
is defined for three conformance classes:
A style sheet is conformant to this specification
if all of its statements that use syntax defined in this module are valid
according to the generic CSS grammar and the individual grammars of each
feature defined in this module.
A renderer is conformant to this specification
if, in addition to interpreting the style sheet as defined by the
appropriate specifications, it supports all the features defined
by this specification by parsing them correctly
and rendering the document accordingly. However, the inability of a
UA to correctly render a document due to limitations of the device
does not make the UA non-conformant. (For example, a UA is not
required to render color on a monochrome monitor.)
An authoring tool is conformant to this specification
if it writes style sheets that are syntactically correct according to the
generic CSS grammar and the individual grammars of each feature in
this module, and meet all other conformance requirements of style sheets
as described in this module.
Requirements for Responsible Implementation of CSS
The following sections define several conformance requirements
for implementing CSS responsibly,
in a way that promotes interoperability in the present and future.
Partial Implementations
So that authors can exploit the forward-compatible parsing rules to assign fallback values, CSS renderers must treat as invalid
(and ignore as appropriate)
any at-rules, properties, property values, keywords, and other syntactic constructs
for which they have no usable level of support.
In particular, user agents must not selectively ignore
unsupported property values and honor supported values in a single multi-value property declaration:
if any value is considered invalid (as unsupported values must be),
CSS requires that the entire declaration be ignored.
Implementations of Unstable and Proprietary Features
Once a specification reaches the Candidate Recommendation stage,
implementers should release an unprefixed implementation
of any CR-level feature they can demonstrate
to be correctly implemented according to spec,
and should avoid exposing a prefixed variant of that feature.
To establish and maintain the interoperability of CSS across
implementations, the CSS Working Group requests that non-experimental
CSS renderers submit an implementation report (and, if necessary, the
testcases used for that implementation report) to the W3C before
releasing an unprefixed implementation of any CSS features. Testcases
submitted to W3C are subject to review and correction by the CSS
Working Group.
This involves defining the "unit" of each set-valued property
(for example, in will-change each keyword is a "unit"),
and ensuring that all of them have a "null" value
(like none). ↵
The "unit" of list-valued properties are much easier to define;
for all but some legacy properties like counter-reset,
it’s just splitting on commas. ↵
Define that, for all of these,
we interpret the property as normal first,
then split it into units for merging;
this isn’t variable-style concatenation.
That is, the following does not define a 2px-offset blue shadow:
As each property is still interpreted as a property,
the first is interpreted the same as box-shadow: 2px 2px; (specifying a single drop-shadow set to currentcolor),
while the second is simply invalid.
More directly but slightly more complex,
we could add a cascade() function
that’s accepted by all set-valued and list-values properties
as a whole "unit".
By default it subs in the previous cascaded value for itself,
but for set-valued things it needs to offer more functionality to do difference/intersection. ↵