Scope Selector Nuance
- Published on:
- Categories:
- CSS Scopes 3, CSS Nesting 4, CSS 61
- Current drink:
- Coffee
In yesterday’s “weekly” I shared the new MDN docs page for @scope
— a new CSS feature currently only available in Chromium-based browsers.
Looking at it made me remember one issue that I had with the @scope
, which was actually intended. I imagine this might be something many people would stumble upon, so here I am, sharing it.
The issue might appear if you’d try to use a selector that would have a mention of a wrapper that exists outside the scope, like this:
The .inner
element now has the pink background — not green. It might be easy to think that it would match if you’d think of scope as of something similar to a media or container query: we really care only about the target element, and the .inner
is both inside our scope, and inside the .outer
, right?
But the issue is: scopes behave closer to native CSS nesting: anything inside would be as if it were nested inside an implicit :scope
element!
Let me quote the corresponding section from the abovementioned MDN docs:
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.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.
So what we get in the end is :scope .outer .inner
— and, of course, we don’t have the .outer
inside our scope! But we can easily confirm this by modifying HTML:
Now, the .inner
element has the green background.
How can we fix this? There are, actually, multiple ways to do so, all using different ways we can create selectors:
The “proper” way to fix this is to just explicitly mention the scope — either by &
or :scope
. This way there won’t be anything implicit added, similar to how &
works in native CSS nesting!
The third example is something that works because the :scope
would be added to the topmost selector, so when the native CSS nesting would expand, it would become .outer :scope .inner3
essentially.
The fourth case is interesting, and not entirely a substitute to others. While it works, it is actually wider than you might think: it would also include the case when the .outer
is inside the .my-scope
or even if both classes are present on the same element:
This is because of how what is inside the :is()
does not really care about any nesting that the element already has — it just checks if the target element is also nested inside the .outer
element.
While this is not a one-to-one to what we intended, knowing this behavior can be useful in other cases.
We cannot use CSS scopes right now, only play with them in Chromium-based browsers, but when they would become more widely available, hopefully knowing this nuance will help you.