Day 102: selecting the scoping root
posted on
It’s time to get me up to speed with modern CSS. There’s so much new in CSS that I know too little about. To change that I’ve started #100DaysOfMoreOrLessModernCSS. Why more or less modern CSS? Because some topics will be about cutting-edge features, while other stuff has been around for quite a while already, but I just have little to no experience with it.
There are different ways of selecting the scoping root inside a @scope
rule.
When you use the :scope
pseudo-class in a stylesheet, it matches the :root
element.
:root {
border: 10px solid red;
}
:scope {
border-color: blue;
}
/* -> 10px blue border on the <html> element */
When you use it inside a scope rule, it matches the rule's scoping root.
<div class="wrapper">
<div class="content">
the cascade is unavoidable
</div>
</div>
@scope (.wrapper) {
:scope {
border: 5px solid red;
}
}
/* -> 5px red border on the .wrapper element */
Selectors inside a scope rule can only match elements that are in scope. Selecting .content
within the .wrapper
scope works:
@scope (.wrapper) {
.content {
background: aqua;
}
}
/* That's like writing .wrapper .content {} */
Selecting .wrapper .content
within the .wrapper
scope doesn't work:
@scope (.wrapper) {
.wrapper .content {
background: aqua;
}
}
/* That's like writing .wrapper .wrapper .content {} */
You can use :scope
instead of .wrapper
. That works because it doesn't match an element with the class .wrapper
inside of .wrapper
, but the scoping root itself.
@scope (.wrapper) {
:scope .content {
background: aqua;
}
}
/* That's like writing .wrapper .content {} */
Instead of :scope
, you can also use &
.
@scope (.wrapper) {
& {
border: 5px solid orange;
}
& .content {
background: aqua;
}
}
/*
-> 5px orange border on the .wrapper element
and aqua background on .content.
*/
There are two differences between :scope
and &
in this context. They're only evident if you have a list of scoping roots.
The first difference in specificity. :scope
has the specificity of a pseudo-class. &
takes on the specificity of the most specific selector in the selector list of scoping roots. In the following example :scope
overrules &
because &
has the specificity of a tag selector.
<section id="section">
<h2>
<span>yo!</span>
</h2>
<p>
<span>yo!</span>
</p>
</section>
@scope (section, p) {
:scope {
border: 10px solid green;
}
& {
border: 10px solid red;
}
}
/* -> 10px green border on section and p */
If you scope the section via its id instead of the tag, &
takes on the specificity of an id selector and thus overrules :scope
.
@scope (#section, p) {
& {
border: 10px solid red;
}
:scope {
border: 10px solid green;
}
}
/* -> 10px red border on the section and p */
The second difference is that :scope
only matches the scoping root itself. &
can match any element that is matched by the selector list.
<section>
<h2>
<span>yo!</span>
</h2>
<p>
<span>yo!</span>
</p>
</section>
@scope (section, p) {
:scope span { background: fuchsia; }
/*
section span { }
p span { }
-> fuchsia background on span within h2 and p
*/
:scope & span { background: aqua; }
/*
section p span { }
-> aqua background only on span within p
*/
:scope :scope span { background: red }
/*
Doesn't match any element because `@scope (section, p)` only
defines multiple scopes, it doesn't nest them.
*/
}
Essentially, that means that :scope
can only match a scoping root and &
can match an element in the selector list, regardless of whether it's considered a scoping root in that context. At least, that's how I interpret it. The spec is still pretty fucking hard to read.
To try out @scope
you have to download Chrome Canary and enable the Experimental Web Platform features
flag in chrome://flags/.
Further reading
Overview: 100 Days Of More Or Less Modern CSS