@scope
Limited availability
This feature is not Baseline because it does not work in some of the most widely-used browsers.
The @scope
CSS at-rule enables you to select elements in specific DOM subtrees, targeting elements precisely without writing overly-specific selectors that are hard to override, and without coupling your selectors too tightly to the DOM structure.
In JavaScript, @scope
can be accessed via the CSS object model interface CSSScopeRule
.
Syntax
The @scope
at-rule contains one or more rulesets (termed scoped style rules) and defines a scope in which to apply them to selected elements. @scope
can be used in two ways:
-
As a standalone block inside your CSS, in which case it includes a prelude section that includes scope root and optional scope limit selectors — these define the upper and lower bounds of the scope.
css@scope (scope root) to (scope limit) { /* … */ }
-
As inline styles included inside a
element in your HTML, in which case the prelude is omitted, and the enclosed ruleset is automatically scoped to the
element's enclosing parent element.
htmlIt is also possible to combine an inline
@scope
with a scope limit selector, as in@scope to (scope limit) { ... }
.
Description
A complex web document might include components such as headers, footers, news articles, maps, media players, ads, and others. As complexity increases, effectively managing the styling for these components becomes more of a concern, and effective scoping of styles helps us manage this complexity. Let's consider the following DOM tree:
body └─ article.feature ├─ section.article-hero │ ├─ h2 │ └─ img │ ├─ section.article-body │ ├─ h3 │ ├─ p │ ├─ img │ ├─ p │ └─ figure │ ├─ img │ └─ figcaption │ └─ footer ├─ p └─ img
If you wanted to select the
element inside the with a class of
article-body
, you could do the following:
- Write a selector like
.feature > .article-body > img
. However, that has high specificity so is hard to override, and is also tightly coupled to the DOM structure. If your markup structure changes in the future, you might need to rewrite your CSS. - Write something less specific like
.article-body img
. However, that will select all images inside thesection
.
This is where @scope
is useful. It allows you to define a precise scope inside which your selectors are allowed to target elements. For example, you could solve the above problem using a standalone @scope
block like the following:
@scope (.article-body) to (figure) {
img {
border: 5px solid black;
background-color: goldenrod;
}
}
The .article-body
scope root selector defines the upper bound of the DOM tree scope in which the ruleset will be applied, and the figure
scope limit selector defines the lower bound. As a result, only
elements inside a with a class of
article-body
, but not inside
elements, will be selected.
Note: This kind of scoping — with an upper and lower bound — is commonly referred to as a donut scope.
The scope's upper bound is inclusive and its lower bound is exclusive. To change this behavior, you can combine either selector with a universal child selector. For example, @scope (scope root) to (scope limit > *)
would make both bounds inclusive, @scope (scope root > *) to (scope limit)
would make both bounds exclusive, while @scope (scope root > *) to (scope limit > *)
would give an exclusive upper bound and an inclusive lower bound.
If you want to select all images inside a with a class of
article-body
, you can omit the scope limit:
@scope (.article-body) {
img {
border: 5px solid black;
background-color: goldenrod;
}
}
Or you could include your @scope
block inline inside a element, which in turn is inside the
with a class of
article-body
:
Note:
It is important to understand that, while @scope
allows you to isolate the application of selectors to specific DOM subtrees, it does not completely isolate the applied styles to within those subtrees. This is most noticeable with inheritance — properties that are inherited by children (for example color
or font-family
) will still be inherited, beyond any set scope limit.
The :scope
pseudo-class
In the context of a @scope
block, the :scope
pseudo-class represents the scope root — it provides an easy way to apply styles to the scope root itself, from inside the scope:
@scope (.feature) {
:scope {
background: rebeccapurple;
color: antiquewhite;
font-family: sans-serif;
}
}
In fact, :scope
is implicitly prepended to all scoped style rules. If you want, you can explicitly prepend :scope
or prepend the nesting selector (&
) to get the same effect if you find these representations easier to understand.
The three rules in the following block are all equivalent in what they select:
@scope (.feature) {
img {
/* … */
}
:scope img {
/* … */
}
& img {
/* … */
}
}
Notes on scoped selector usage
-
A scope limit can use
:scope
to specify a specific relationship requirement between the scope limit and root. For example:css/* figure is only a limit when it is a direct child of the :scope */ @scope (.article-body) to (:scope > figure) { /* … */ }
-
A scope limit can reference elements outside the scope root using
:scope
. For example:css/* figure is only a limit when the :scope is inside .feature */ @scope (.article-body) to (.feature :scope figure) { /* … */ }
-
Scoped style rules can't escape the subtree. Selections like
:scope + p
are invalid because that selection would be outside the subtree. -
It is perfectly valid to define the scope root and limit as a selector list, in which case multiple scopes will be defined. In the following example the styles are applied to any
inside awith a class of
article-hero
orarticle-body
, but not if it is nested inside a
:css@scope (.article-hero, .article-body) to (figure) { img { border: 5px solid black; background-color: goldenrod; } }
Specificity in @scope
Including a ruleset inside a @scope
block does not affect the specificity of its selector, regardless of the selectors used inside the scope root and limit. For example:
@scope (.article-body) {
/* img has a specificity of 0-0-1, as expected */
img {
/* … */
}
}
However, if you decide to explicitly prepend the :scope
pseudo-class to your scoped selectors, you'll need to factor it in when calculating their specificity. :scope
, like all regular pseudo-classes, has a specificity of 0-1-0. For example:
@scope (.article-body) {
/* :scope img has a specificity of 0-1-0 + 0-0-1 = 0-1-1 */
:scope img {
/* … */
}
}
When using the &
selector inside a @scope
block, &
represents the scope root selector; it is internally calculated as that selector wrapped inside an :is()
pseudo-class function. So for example, in:
@scope (figure, #primary) {
& img {
/* … */
}
}
& img
is equivalent to :is(figure, #primary) img
. Since :is()
takes the specificity of its most specific argument (#primary
, in this case), the specificity of the scoped & img
selector is therefore 1-0-0 + 0-0-1 = 1-0-1.
The difference between :scope
and &
inside @scope
:scope
represents the matched scope root, whereas &
represents the selector used to match the scope root. Because of this, it is possible to chain &
multiple times. However, you can only use :scope
once — you can't match a scope root inside a scope root.
@scope (.feature) {
/* Selects a .feature inside the matched root .feature */
& & {
/* … */
}
/* Doesn't work */
:scope :scope {
/* … */
}
}
How @scope
conflicts are resolved
@scope
adds a new criterion to the CSS cascade: scoping proximity. This states that when two scopes have conflicting styles, the style that has the smallest number of hops up the DOM tree hierarchy to the scope root is applied. Let's look at an example to see what this means.
Take the following HTML snippet, where different-themed cards are nested inside one another:
Light theme text
Dark theme text
Light theme text
If you wrote the theme CSS like so, you'd run into trouble:
.light-theme {
background: #ccc;
}
.dark-theme {
background: #333;
}
.light-theme p {
color: black;
}
.dark-theme p {
color: white;
}
The innermost paragraph is supposed to be colored black because it is inside a light theme card. However, it's targeted by both .light-theme p
and .dark-theme p
. Because the .dark-theme p
rule appears later in the source order, it is applied, and the paragraph ends up being wrongly colored white.
To fix this, you can use @scope
as follows:
@scope (.light-theme) {
:scope {
background: #ccc;
}
p {
color: black;
}
}
@scope (.dark-theme) {
:scope {
background: #333;
}
p {
color: white;
}
}
Now the innermost paragraph is correctly colored black. This is because it is only one DOM tree hierarchy level away from the .light-theme
scope root, but two levels away from the .dark-theme
scope root. Therefore, the light style wins.
Note: Scoping proximity overrules source order but is itself overridden by other, higher-priority criteria such as importance, layers, and specificity.
Formal syntax
Examples
Basic style inside scope roots
In this example, we use two separate
MDN contains lots of information about
HTML,
CSS, and
JavaScript.
MDN contains lots of information about
HTML,
CSS, and
JavaScript.
The above code renders like this: In this example, we have an HTML snippet that matches the DOM structure we talked about earlier in the Description section. This structure represents a typical article summary. The key features to note are the The aim of this example is to show how to use a scope root and limit to style
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam euismod
consectetur leo, nec eleifend quam volutpat vitae. Duis quis felis at
augue imperdiet aliquam. Morbi at felis et massa mattis lacinia. Cras
pharetra velit nisi, ac efficitur magna luctus nec.
Lorem ipsum dolor sit amet, consectetur adipiscing elit. In our CSS, we have two In the rendered code, note how all of the @scope
blocks to match links inside elements with a .light-scheme
and .dark-scheme
class respectively. Note how :scope
is used to select and provide styling to the scope roots themselves. In this example, the scope roots are the HTML
CSS
@scope (.light-scheme) {
:scope {
background-color: plum;
}
a {
color: darkmagenta;
}
}
@scope (.dark-scheme) {
:scope {
background-color: darkmagenta;
color: antiquewhite;
}
a {
color: plum;
}
}
Result
Scope roots and scope limits
elements, which are nested at various levels in the structure.
elements starting at the top of the hierarchy, but only down as far as (and not including) the
inside the
element — in effect creating a donut scope.HTML
Article heading
Article subheading
CSS
@scope
blocks:
@scope
block defines its scope root as elements with a class of .feature
(in this case, the outer only), demonstrating how
@scope
can be used to theme a specific HTML subset.@scope
block also defines its scope root as elements with a class of .feature
, but also defines a scope limit of figure
. This ensures that contained rulesets will only be applied to matching elements within the scope root (
in this case) that are not nested inside descendant
elements. This @scope
block contains a single ruleset that styles
elements with a thick black border and a golden background color./* Scoped CSS */
@scope (.feature) {
:scope {
background: rebeccapurple;
color: antiquewhite;
font-family: sans-serif;
}
figure {
background-color: white;
border: 2px solid black;
color: black;
padding: 10px;
}
}
/* Donut scope */
@scope (.feature) to (figure) {
img {
border: 5px solid black;
background-color: goldenrod;
}
}
Result
elements are styled with the thick border and golden background, except for the one inside the
element (labeled "My infographic").Specifications
Specification CSS Cascading and Inheritance Level 6
# scoped-stylesBrowser compatibility
See also
:scope
CSSScopeRule
@scope
at-rule on developer.chrome.com (2023)