A quick introduction to CSS @scope

CSS @scope example

Have you heard about @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 over at https://codepen.io/bramus/pen/dyKjbox.

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 …

Screenshot of visual output

~

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.

Visualization of the Cascade, with “Order of Appearance” highlighted.

~

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?! 🤩

Visualization of the Cascade, with “Scope Proximity” injected and highlighted.

~

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!

Screenshot of correct output, with the third link being red.

~

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, but Mozilla/Firefox is not convinced about it yet

~

This post originally was a thread on Twitter and on Mastodon. 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. More on that in a future post. 😉

~

Published by Bramus!

Bramus is a frontend web developer from Belgium, working as a Chrome Developer Relations Engineer at Google. From the moment he discovered view-source at the age of 14 (way back in 1997), he fell in love with the web and has been tinkering with it ever since (more …)

Unless noted otherwise, the contents of this post are licensed under the Creative Commons Attribution 4.0 License and code samples are licensed under the MIT License

Join the Conversation

3 Comments

  1. This sounds like it would be useful for nesting situations as you’ve demonstrated. I have a lot of styles that exist purely to reset or kill inherited styles from parents of unrelated components. This seems like a nice half-way point between light DOM and shadow DOM for those of us who work in a controlled environment and don’t want to take on all the baggage that come with shadow DOM (but would still like some of the encapsulation).

    1. Yes, but then your DOM can’t change. With @scope you have the freedom to move elements around in the tree, without needing to adjust your CSS.

      That said, if that’s no issue to you then you don’t need @scope

Leave a comment

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.