Contextual Styling With CSS Nesting
I've been hooked on CSS nesting for awhile. Like Sass, I've always written it with the & selector at the front, like this.
.button {
background: slategrey;
&:hover {
background: darkslategrey;
}
}
Turns out, the & doesn't have to come first. Put it after another selector and the relationship flips. Instead of styling what's inside the element, you're styling the element based on what surrounds it.
.card {
background: white;
color: black;
/* When the card is inside a dark container */
.dark & {
background: #1c1e21;
color: white;
}
}
That compiles to the equivalent of this.
.card {
background: white;
color: black;
}
.dark .card {
background: #1c1e21;
color: white;
}
Read .dark & as "when this element is inside .dark." The card's default styles and its dark mode styles now live in the same rule. This is handy for components that adapt to their container. Here's a button that slims down when it lands in a toolbar.
.button {
padding: 0.875rem 1.125rem;
.toolbar & {
padding: 0.375rem 0.625rem;
}
}
And since & is just a selector like any other, you can use it more than once. This adds spacing between adjacent cards.
.card {
border: solid 1px lightgray;
border-radius: 0.5rem;
padding: 1rem;
& + & {
margin-top: 1rem;
}
}
The caveat is specificity. Under the hood, the browser replaces & with the parent selector wrapped in :is(), so .dark & becomes .dark :is(.card). That matters because :is() takes the specificity of its most specific argument, which can bite you if the parent rule uses a selector list.
If you've used Sass, this trick probably looks familiar because it's worked there for ages. The two aren't quite the same underneath, though. Sass pastes the parent selector in as text, so & inherits whatever specificity that text happens to have.
Native CSS wraps it in :is() instead, which is where the selector list gotcha comes from. The examples here land in the same place either way, but the moment an ID shows up in the parent, the two stop agreeing.
The good news is that browser support for native nesting is green across the board now, so you don't need a preprocessor for it anymore.
That's one less reason to reach for a build step when writing CSS.