
A quick introduction to CSS @scope
A quick introduction to CSS @scope êŽë š

Have you heard about #CSS @scope? Itâs an upcoming way to scope the reach of your CSS selectors, allowing you to move away from methodologies such as BEM because you no longer need to name those in-between elements.
Itâs coming to Chrome 118, so letâs take a closer look âŠ
UPDATE 2023.10.04
Iâve published a more in-depth article covering @scope over at developer.chrome.com. You should check that one out instead of this quick intro.
Letâs start with an example showing why youâd need @scope.
Say you have the markup as seen in the demo below
HTML:
<div class="light">
<p><a href="âŠ">What color am I?</a></p>
<div class="dark">
<p><a href="âŠ">What about me?</a></p>
<div class="light">
<p><a href="âŠ">Am I the same as the first?</p>
</div>
</div>
</div>
CSS:
.light { background: #ccc; }
.dark { background: #333; }
.light a { color: red; }
.dark a { color: yellow; }
In the browser, that third link will be yellow, not red âŠ

This because of how the CSS cascade works.
The used selectors have the same specificity, so the cascade moves on to âOrder of appearanceâ.
Because of that, .dark a gets applied last. And since colors inherit, the link inside that nested .light will also be yellow.

This is where @scope can fix things. You use it to scope style rules in CSS, with the application of weak scoping proximity between the scoping root and the subject of each style rule.
(The scoping root is the part between the parens of @scope â here .light or .dark)
@scope (.light) {
:scope { background: white; }
a { color: blue;}
}
@scope (.dark) {
:scope { background: black; }
a { color: #ccf; }
}
The reason that this works is because scoping is an extra step in the cascade. Declarations of the same specificity are weighted by proximity to their scoping root before falling back to source order. How cool is that?! đ€©

So in our example, the visual output will now be correct: links inside .light will red and links inside .dark will be yellow. This because they look up the nearest scope root, yay!

The cool thing about @scope is that you can also set a scope end, to create a âdonut scopeâ.
@scope (.media-object) to (.content) {
img {...}
video {...}
}
The selectors inside @scope block only target the elements between the start and end of the scope.
So when combined with the markup below, the CSS wonât target the img inside .content
<div class="media-object">
<img>
<div class="content">
...arbitrary stuffâŠ
<img>
...arbitrary stuffâŠ
</div>
</div>
This allows you to move away from methodologies such as BEM, where you would have named the image in the header something like .media-object__header__image, or complex selectors that rely on a bunch of combinators.
With @scope you no longer need to, you simply cut off the reach of the selectors using the scope end đ
One important note to make is that @scope limits the reach of the selectors, it does not stop/prevent inheritance.
For example, a color declaration inside a donut-scope will still inherit onto children deeper down the tree that located outside of the scope end.
CSS Scope is shipping with Chrome 118, which gets a stable release this October. You can already try it out in Chrome Canary today.
WebKit/Safari have expressed their support for this API (WebKit/standards-positions), but Mozilla/Firefox is not convinced about it yet (mozilla/standards-positions) âŠ
Info
This post originally was a thread on Twitter (bramus) and on Mastodon (@bramus). Feel free to repost those threads to give them more reach.
Not covered in this post are the meaning of :scope (+ how it differs from &) and a prelude-less @scope (bramus). More on that in a future post. đ