1. Introduction and Missing Sections
This is a diff spec over CSS Cascading and Inheritance Level 5. It is currently an Exploratory Working Draft: if you are implementing anything, please use Level 5 as a reference. We will merge the Level 5 text into this draft once it reaches CR.
2. Cascading
The cascade takes an unordered list of declared values for a given property on a given element, sorts them by their declaration’s precedence as determined below, and outputs a single cascaded value.
2.1. Cascade Sorting Order
The cascade sorts declarations according to the following criteria, in descending order of precedence:
- Origin and Importance
-
The origin of a declaration is based on where it comes from
and its importance is
whether or not it is declared with !important (see below).
The precedence of the various origins is, in descending order:
- Transition declarations [css-transitions-1]
- Important user agent declarations
- Important user declarations
- Important author declarations
- Animation declarations [css-animations-1]
- Normal author declarations
- Normal user declarations
- Normal user agent declarations
Declarations from origins earlier in this list win over declarations from later origins.
- Context
-
A document language can provide for blending declarations sourced
from different encapsulation contexts,
such as the nested tree contexts of shadow trees in the [DOM].
When comparing two declarations that are sourced from different encapsulation contexts, then for normal rules the declaration from the outer context wins, and for important rules the declaration from the inner context wins. For this purpose, [DOM] tree contexts are considered to be nested in shadow-including tree order.
Note: This effectively means that normal declarations belonging to an encapsulation context can set defaults that are easily overridden by the outer context, while important declarations belonging to an encapsulation context can enforce requirements that cannot be overridden by the outer context.
- The Style Attribute
- Separately for normal and important declarations, declarations that are attached directly to an element (such as the contents of a style attribute) rather than indirectly mapped by means of a style rule selector take precedence over declarations the same importance that are mapped via style rule.
- Layers
-
Declarations within each origin and context can be explicitly assigned to a cascade layer.
For the purpose of this step,
any declaration not assigned to an explicit layer is added to an implicit final layer.
Cascade layers (like declarations) are sorted by order of appearance, see § 2.4.1 Layer Ordering. When comparing declarations that belong to different layers, then for normal rules the declaration whose cascade layer is latest in the layer order wins, and for important rules the declaration whose cascade layer is earliest wins.
Note: This follows the same logic used for precedence of normal and important origins, thus the !important flag maintains the same “override” purpose in both settings.
- Strong Scoping Proximity
-
If two declarations both have elements
selected by scoped descendant relationships
applying strong scoping proximity,
then the declaration with the fewest generational hops
between the ancestor/descendant element pair wins.
If multiple such pairs are represented, their strong scoping proximity weights are compared from innermost scoping relationship to outermost scoping relationship (with any missing pairs weighted as infinity).
- Specificity
- The Selectors module [SELECT] describes how to compute the specificity of a selector. Each declaration has the same specificity as the style rule it appears in. The declaration with the highest specificity wins.
- Weak Scoping Proximity
-
If two declarations both have elements
selected by scoped descendant relationships
applying weak scoping proximity,
then the declaration with the fewest generational hops
between the ancestor/descendant element pair wins.
If multiple such pairs are represented, their weak scoping proximity weights are compared from innermost scoping relationship to outermost scoping relationship (with any missing pairs weighted as infinity).
- Order of Appearance
-
The last declaration in document order wins.
For this purpose:
- Style sheets are ordered as in final CSS style sheets.
- Declarations from imported style sheets are ordered as if their style sheets were substituted in place of the @import rule.
- Declarations from style sheets independently linked by the originating document are treated as if they were concatenated in linking order, as determined by the host document language.
- Declarations from style attributes are ordered according to the document order of the element the style attribute appears on, and are all placed after any style sheets. [CSSSTYLEATTR]
Does scope proximity belong above or below specificity in the cascade? [Issue #6790]
The output of the cascade is a (potentially empty) sorted list of declared values for each property on each element.
2.2. Cascading Origins
CSS Cascading 5 § 6.2 Cascading Origins
cascade origin
2.3. Important Declarations: the !important annotation
CSS Cascading 5 § 6.3 Important Declarations: the !important annotation
important normal
2.4. Cascade Layers
CSS Cascading 5 § 6.4 Cascade Layers
2.4.1. Layer Ordering
CSS Cascading 5 § 6.4.3 Layer Ordering
2.5. Scoping Styles: the @scope rule
A scope is a subtree or fragment of a document, which can be used by selectors for more targeted matching. A scope is formed by determining:
-
The scoping root node, which acts as the upper bound of the scope, and optionally:
-
The scoping limit elements, which act as the lower bounds.
An element is in scope if:
-
It is an inclusive descendant of the scoping root, and
-
It is not an inclusive descendant of a scoping limit.
Note: In contrast to Shadow Encapsulation, which describes a persistent one-to-one relationship in the DOM between a shadow host and its nested shadow tree, multiple overlapping scopes can be defined in relation to the same elements.
Scoped styles are described in CSS using the @scope block at-rule, which declares a scoping root and optional scoping limits associated with a set of style rules.
@scope ( .light-scheme) { /* Only match links inside a light-scheme */ a{ color : darkmagenta; } } @scope ( .dark-scheme) { /* Only match links inside a dark-scheme */ a{ color : plum; } } @scope ( .media-object) { /* Only match author images inside a media-object */ .author-image{ border-radius : 50 % ; } }
@scope ( .media-object) to( .content > *) { img{ border-radius : 50 % ; } .content{ padding : 1 em ; } }
The img selector will only match image tags that are in a DOM fragment starting with any .media-object, and including all descendants up to any intervening children of the .content class.
Should scoping limits be added to the definition of scoped selectors?
2.5.1. Effects of @scope
The @scope at-rule has three primary effects on the style rules it contains:
-
The style rules in an @scope <stylesheet> are scoped style rules.
-
The :scope selector is defined to match the @scope rule’s scoping root. The & selector is defined to represent the selector representing the scoping root (the <scope-start> selector), or else :scope if no selector was specified.
-
The cascade prioritizes declarations with a more proximate scoping root, regardless of specificity or source order by applying weak scoping proximity between the scoping root and the subject of each scoped style rule.
Should @scope use strong or weak scoping proximity? Strong scoping proximity causes declarations to be weighted more strongly by scope proximity than by their selector’s specificity. Weak scoping proximity causes declarations of the same specificity to be weighted by proximity to their scoping root before falling back to source ordering, but declarations of higher specificity win over more tightly-scoped declarations. The Working Group currently leans towards weak proximity, and recommends that as a starting point for prototypes. [Issue #6790]
Note: Unlike Nesting, selectors within an @scope rule do not acquire the specificity of any parent selector(s) in the @scope prelude.
@scope ( #hero) { img{ border-radius : 50 % ; } } :where ( #hero) img{ border-radius : 50 % ; }
The additional specificity of the #hero selector
is not applied to the specificity of the scoped selector.
However, since one img
selector is scoped,
that selector is weighted more strongly in the cascade
with the application of weak scoping proximity.
main-component
and sub-component
)
and every element is marked as part of one or both scopes
using the data-scope
attribute:
< section data-scope = "main-component" > < p data-scope = "main-component" > ...< p > <!-- sub-component root is in both scopes --> < section data-scope = "main-component sub-component" > <!-- children are only in the inner scope --> < p data-scope = "sub-component" > ...< p > </ section > </ section >
Those custom scope attributes are then appended to every single selector in CSS:
p[ data-scope~='main-component' ] { color : red; } p[ data-scope~='sub-component' ] { color : blue; } /* both sections are part of the outer scope */ section[ data-scope~='main-component' ] { background : snow; } /* the inner section is also part of the inner scope */ section[ data-scope~='sub-component' ] { color : ghostwhite; }
Using the @scope rule, authors and tools can replicate similar behavior with the unique attribute or class applied only to the scoping roots:
< section data-scope = "main-component" > < p > ...< p > < section data-scope = "sub-component" > < p > ...< p > </ section > </ section >
Then the class or attribute can be used for establishing both upper and lower boundaries. Elements matched by a lower boundary selector are excluded from the resulting scope, which allows authors to create non-overlapping scopes by default:
@scope ([ data-scope='main-component' ]) to([ data-scope]) { p{ color : red; } /* only the outer section is part of the outer scope */ section{ background : snow; } } @scope ([ data-scope='sub-component' ]) to([ data-scope]) { p{ color : blue; } /* the inner section is only part of the inner scope */ section{ color : ghostwhite; } }
However, authors can use the child combinator and universal selector to create scope boundaries that overlap, such that the inner scope root is part of both scopes:
@scope ([ data-scope='main-component' ]) to([ data-scope] > *) { p{ color : red; } /* both sections are part of the outer scope */ section{ background : snow; } }
2.5.2. Syntax of @scope
The syntax of the @scope rule is:
@scope [(<scope-start>)]? [to (<scope-end>)]? { <stylesheet> }
where:
-
<scope-start> is a <forgiving-selector-list> selector used to identify the scoping root(s).
-
<scope-end> is a <forgiving-selector-list> selector used to identify any scoping limits.
-
the <stylesheet> represents the scoped style rules.
Pseudo-elements cannot be scoping roots or scoping limits; they are invalid both within <scope-start> and <scope-end>.
2.5.3. Scoped Style Rules
Scoped style rules differ from non-scoped rules in the following ways:
-
Their selectors can only match elements that are in scope. (This only applies to the subject; the rest of the selector can match unrestricted.)
-
They accept a <relative-selector-list> as their prelude (rather than just a <selector-list>). Such relative selectors are relative to :scope.
-
Any selector in the <relative-selector-list> that does not start with a combinator but does contain the nesting selector or the :scope selector, is interpreted as a non-relative selector (but the subject must still be in scope to match).
@scope ( #my-component) { p{ color : green; } :scope p{ color : green; } }
Authors can adjust the implied relationship by adding an explicit combinator:
@scope ( #my-component) { > p{ color : green; } :scope > p{ color : green; } }
Authors can also target or explicitly position the scoping root in a selector by including either :scope or & in a given selector:
@scope ( #my-component) { :scope{ border : thin solid; } &{ border : thin solid; } main :scope p{ color : green; } main & p{ color : green; } }
While the :scope or & selectors can both refer to the scoping root, they have otherwise different meanings in this context:
- Differences in selector matching
-
The :scope selector will only match the scoping root itself, while the & selector is able to match any element that is matched by the <scope-start> selector list.
- Differences in selector specificity
-
The :scope selector has a specificity equal to other pseudo-classes, while the & selector has the specificity equal to the most specific selector in <scope-start>.
2.5.4. Identifying Scoping Roots and Limits
A @scope rule produces one or more scopes as follows:
- Finding the scoping root(s)
-
For each element matched by <scope-start>, create a scope using that element as the scoping root. If no <scope-start> is specified, the scoping root is the parent element of the owner node of the stylesheet where the @scope rule is defined. (If no such element exists, then the scoping root is the root of the containing node tree.) Any :scope or & selectors in <scope-start> are interpreted as defined for its outer context.
- Finding any scoping limits
-
For each scope created by a scoping root, its scoping limits are set to all elements that are in scope and that match <scope-end>, interpreting :scope and & exactly as in scoped style rules.
style
elements by leaving out the <scope-start> selector.
For example:
< div > < style > @ scope { p { color : red ; } } </ style > < p > this is red</ p > </ div > < p > not red</ p >
That would be equivalent to:
< div id = "foo" > < style > @ scope ( # foo ) { p { color : red ; } } </ style > < p > this is red</ p > </ div > < p > not red</ p >
/* .content is only a limit when it is a direct child of the :scope */ @scope ( .media-object) to( :scope > .content) { ...}
Scoping limits can also reference elements outside their scoping root by using :scope. For example:
/* .content is only a limit when the :scope is inside .sidebar */ @scope ( .media-object) to( .sidebar :scope .content) { ...}
2.5.5. Scope Nesting
@scope rules can be nested. In this case, just as with the nested style rules, the selectors of the inner @scope (including those defining its scope) are scoped by the selectors of the outer one.
@scope ( .parent-scope) { @scope ( :scope > .child-scope) to( :scope .limit) { :scope .content{ color : red; } } }
is equivalent to:
@scope ( .parent-scope > .child-scope) to( .parent-scope > .child-scope .limit) { .parent-scope > .child-scope .content{ color : red; } }
Global name-defining at-rules such as @keyframes or @font-face or @layer that are defined inside @scope are valid, but are not scoped or otherwise affected by the enclosing @scope rule. However, any style rules contained by such rules (e.g. within @layer) are scoped.
2.6. Scoped Descendant Combinator
The scoped descendant combinator describes a descendant relationship between two elements.
A selector of the form A >> B represents
an element B
that is an arbitrary descendant
of some ancestor element A
.
This combinator differs from the descendant combinator in that it applies weak scoping proximity to the relationship between A and B. It does not change the :scope element.
Should the scoped descendant combinator use strong or weak scoping proximity? Should it even exist? It’s defined here to work the way many people expected the regular descendant combinator to work...
In this example
the <a>
element’s color will be determined
by the nearest ancestor with either
a light-scheme or dark-scheme class.
(If the descendant selector had been used,
its color would always be plum,
because it is later in the source order.)
.light-scheme >> a { color: darkmagenta; } .dark-scheme >> a { color: plum; }
However if the <a>
element has a light-scheme ancestor and is focused,
its color will be teal even if it has a nearer dark-scheme ancestor,
because there is no equivalent dark-scheme rule.
.light-scheme >> a:focus { color: teal; }
2.7. Precedence of Non-CSS Presentational Hints
CSS Cascading 5 § 6.4 Cascade Layers
3. CSSOM
3.1. The CSSScopeRule
interface
The CSSScopeRule
interface represents the @scope rule:
[Exposed =Window ]interface :
CSSScopeRule CSSGroupingRule {readonly attribute CSSOMString ;
start readonly attribute CSSOMString ; };
end
start
of typeCSSOMString
-
The
start
attribute must return a value as follows:- The @scope rule has an associated <scope-start>
- The result of serializing that <scope-start>.
- Otherwise
- An empty string.
end
of typeCSSOMString
-
The
end
attribute must return a value as follows:- The @scope rule has an associated <scope-end>
- The result of serializing that <scope-end>.
- Otherwise
- An empty string.
4. Changes
This appendix is informative.
4.1. Changes since the 21 December 2021 First Public Working Draft
Significant changes since the 21 December 2021 First Public Working Draft include:
-
Clarified @scope effects on nested :scope and & selectors. (Issue 8377)
-
Removed @scope prelude from specificity calculation. (Issue 8500)
-
Specified how name-defining at-rules behave in @scope. (Issue 6895)
-
Added the
CSSScopeRule
interface. -
Added implicit scopes by making ''<scope-start>'' optional. (Issue 6606)
-
Disallowed pseudo-elements in the @scope prelude. (Issue 7382)
-
Removed selector scoping notation. (Issue 7709)
-
Scoping limit elements are excluded from the resulting scope. (Issue 6577)
4.2. Additions Since Level 5
The following features have been added since Level 5:
-
The definition of a scope, as described by a combination of <scope-start> and <scope-end> selectors.
-
The in-scope (:in()) pseudo-class for selecting with lower-boundaries
-
The @scope rule for creating scoped stylesheets
-
The definition of scope proximity in the cascade
4.3. Additions Since Level 4
The following features have been added since Level 4:
-
Added cascade layers to the cascade sort criteria (and defined style attributes as a distinct step of the cascade sort criteria so that they interact appropriately).
-
Introduced the @layer rule for defining cascade layers.
-
Added layer/layer() option to @import definition.
-
Introduced the revert-layer keyword for rolling back values to previous layers.
4.4. Additions Since Level 3
The following features have been added since Level 3:
-
Introduced revert keyword, for rolling back the cascade.
-
Introduced supports() syntax for supports-conditional @import rules.
-
Added encapsulation context to the cascade sort criteria to accommodate Shadow DOM. [DOM]
-
Defined the property two aliasing mechanisms CSS uses to support legacy syntaxes. See CSS Cascading 4 § 3.1 Property Aliasing.
4.5. Additions Since Level 2
The following features have been added since Level 2:
- The all shorthand
- The initial keyword
- The unset keyword
- Incorporation of animations and transitions into the cascade.
Acknowledgments
David Baron, Tantek Çelik, Keith Grant, Giuseppe Gurgone, Theresa O’Connor, Florian Rivoal, Noam Rosenthal, Simon Sapin, Jen Simmons, Nicole Sullivan, Lea Verou, and Boris Zbarsky contributed to this specification.
5. Privacy Considerations
-
User preferences and UA defaults expressed via application of style rules are exposed by the cascade process, and can be inferred from the computed styles they apply to a document.
6. Security Considerations
-
The cascade process does not distinguish between same-origin and cross-origin stylesheets, enabling the content of cross-origin stylesheets to be inferred from the computed styles they apply to a document.
-
The @import rule does not apply the CORS protocol to loading cross-origin stylesheets, instead allowing them to be freely imported and applied.
-
The @import rule assumes that resources without
Content-Type
metadata (or any same-origin file if the host document is in quirks mode) aretext/css
, potentially allowing arbitrary files to be imported into the page and interpreted as CSS, potentially allowing sensitive data to be inferred from the computed styles they apply to a document.