CSS Cascading and Inheritance Module Level 6

Editor’s Draft,

More details about this document
This version:
https://drafts.csswg.org/css-cascade-6/
Latest published version:
https://www.w3.org/TR/css-cascade-6/
Previous Versions:
Feedback:
CSSWG Issues Repository
Inline In Spec
Editors:
Elika J. Etemad / fantasai (Apple)
Miriam E. Suzanne (Invited Expert)
Tab Atkins Jr. (Google)
Suggest an Edit for this Spec:
GitHub Editor

Abstract

This CSS module describes how to collate style rules and assign values to all properties on all elements. By way of cascading and inheritance, values are propagated for all properties on all elements.

New in this level is § 2.5 Scoping Styles: the @scope rule.

CSS is a language for describing the rendering of structured documents (such as HTML and XML) on screen, on paper, etc.

Status of this document

This is a public copy of the editors’ draft. It is provided for discussion only and may change at any moment. Its publication here does not imply endorsement of its contents by W3C. Don’t cite this document other than as work in progress.

Please send feedback by filing issues in GitHub (preferred), including the spec code “css-cascade” in the title, like this: “[css-cascade] …summary of comment…”. All issues and comments are archived. Alternately, feedback can be sent to the (archived) public mailing list [email protected].

This document is governed by the 18 August 2025 W3C Process Document.

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:
  1. Transition declarations [css-transitions-1]
  2. Important user agent declarations
  3. Important user declarations
  4. Important author declarations
  5. Animation declarations [css-animations-1]
  6. Normal author declarations
  7. Normal user declarations
  8. 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.

Specificity
The Selectors module [SELECT] describes how to compute the specificity of a selector.

When comparing declarations from two style rules, the declaration belonging to the style rule with the highest specificity wins.

Scope Proximity
When comparing declarations that appear in style rules with different scoping roots, then the declaration with the fewest generational or sibling-element hops between the scoping root and the scoped style rule subject wins. For this purpose, style rules without a scoping root are considered to have infinite proximity hops.
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]

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:

An element is in scope if:

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.

For example, an author might have wide-reaching color-scheme scopes, which overlap more narrowly-scoped design patterns such as a media object. The selectors in the @scope rule establish scoping root and optional scoping limit elements, while the nested selectors only match elements that are in a resulting scope:
@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%; }
}
By providing scoping limits, an author can limit matching more deeply nested descendants. For example:
@scope (.media-object) to (.content > *) {
  img { border-radius: 50%; }
  .content { padding: 1em; }
}

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:

Note: Unlike Nesting, selectors within an @scope rule do not acquire the specificity of any parent selector(s) in the @scope prelude.

The following selectors have the same specificity (0,0,1):
@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 scope proximity.

Many existing tools implement "scoped styles" by applying a unique class or attribute to every element in a given scope or "single file component." In this example there are two scopes (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>)]? {
  <block-contents>
}

where:

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:

By default, selectors in a scoped style rule are relative selectors, with the scoping root and descendant combinator implied at the start. The following selectors will match the same elements:
@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; }
}

Both the :scope and & selectors match the scope root, but with different specificity: :scope has as specificity of (0,1,0), whereas & has a specificity of 0.

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 and the containing node tree is a shadow tree, then the scoping root is the shadow host. Otherwise, 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.

Authors can establish local scoping for 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>
Scoping limits can use the :scope pseudo-class to require a specific relationship to the scoping root:
/* .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 prelude selectors of the inner @scope (those defining its scope) are scoped by the selectors of the outer one.

Note: The resulting scope for further nested scoped style rules is practically constrained by both the outer and inner @scope rules, but the scoping root is defined by the innermost @scope. Since scope proximity is measured between a scoped style rule subject and scoping root, only the innermost @scope matters for determining scope proximity of nested @scope rules.

Should the scope proximity calculation be impacted by nesting scopes? [Issue #10795]

When nesting @scope rules inside other @scope rules, or inside other selectors, the <scope-start> selector is relative to the nesting context, while the <scope-end> and any scoped style rules are relative to the scoping root For example, the following code:
@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.5.6. Scoped Declarations

Declarations may be used directly with the body of a @scope rule. Contiguous runs of declarations are wrapped in nested declarations rules, which match the scoping root with zero specificity.

@scope (.foo) {
  border: 1px solid black;
}

is equivalent to:

@scope (.foo) {
  :where(:scope) {
    border: 1px solid black;
  }
}

Just like for style rules, declarations and child rules may be mixed within @scope.

2.6. 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 type CSSOMString
The start attribute returns the result of serializing the <scope-start> of the rule (without the enclosing parentheses), or null if there is no <scope-start>.
end of type CSSOMString
The end attribute returns the result of serializing the <scope-end> of the rule (without the enclosing parentheses), or null if there is no <scope-end>.

4. Changes

This appendix is informative.

4.1. Changes since the 21 March 2023 Working Draft

Significant changes since the 21 March 2023 Working Draft include:

4.2. Changes since the 21 December 2021 First Public Working Draft

Significant changes since the 21 December 2021 First Public Working Draft include:

4.3. Additions Since Level 5

The following features have been added since Level 5:

4.4. Additions Since Level 4

The following features have been added since Level 4:

4.5. Additions Since Level 3

The following features have been added since Level 3:

4.6. Additions Since Level 2

The following features have been added since Level 2: