Style Queries are Almost Like Mixins (But Mixins Would Be Better)
Style Queries are Almost Like Mixins (But Mixins Would Be Better) êŽë š
I was styling a menu thing the other day, and it had some decently nested selectors. Normally Iâm a pretty big fan of putting a class right on the thing you want to style and keeping CSS selectors pretty âflatâ in that they are just that class alone. But for menus and the semantic HTML within, a little nesting seemed reasonable. For instance this HTML:
<nav class="site-nav">
<ul>
<li><a href="#">Home</a></li>
<li><a href="#">Contact</a></li>
<li><a href="#">About</a></li>
<li><a href="#">History</a></li>
</ul>
</nav>
Can lead to this kind of CSS:
.site-nav {
> ul {
> li {
> a {
&:hover,
&:focus {
}
}
}
}
}
I can see how that turns some people off, but honestly it doesnât bother me that much. The structure is reliable here and Iâd rather this setup in CSS than a class on every one of those links in the HTML.
If we get into *sub-*menu territory though, it gets gnarlier:
.site-nav {
> ul {
> li {
> ul { /* sub menu */
> li {
> a {
&:hover,
&:focus {
}
}
}
}
}
}
}
Iâm willing to admit this is probably a bit too far in nesting town đŹ. Particularly because at each of those nested levels there will be a bunch of styling and it will become hard to reason about quite quickly.
It occurred to me that the (newfangled, not production-ready) CSS style queries might be able to jump in and help here, because they behave a bit like a mixin.
Mixin?
Yeah! Thatâs what Sass called the concept, anyway. A mixin allows us to name a block of styles, then call them as needed. SoâŠ
@mixin linkHovered {
background: red;
color: white;
}
.site-nav {
> ul {
> li {
> a {
&:hover,
&:focus {
@include linkHovered;
}
}
}
}
}
So now weâve kind of flattened out the styles a bit. We have this re-usable chunk of styles that we can just call rather than nest the styles so deeply.
What I wanted to try here was using Style Queries (and not Sass), but unfortunately itâs not quite as clean as Iâd like. After using Sass for so long, this is what I wanted to work:
/* Invalid! Doesn't select anything */
@container style(--linkHovered) {
background: red;
}
.site-nav {
> ul {
> li {
> a {
&:hover, &:focus {
--linkHovered: true;
}
}
}
}
}
But thatâs a no-go. The @container
style query either needs to be nested within the other styles so that it has an implied selector (which defeats the âflatten the stylesâ purpose) or it needs an explicit selector inside it. So it needs to be like this:
@container style(--hasLinkHovered) {
a {
background: red;
}
}
.site-nav {
> ul {
> li {
&:has(> a:hover, > a:focus) {
--hasLinkHovered: true;
}
}
}
}
That works (where supported):
I just donât love it. You have to set the Custom Property higher up in the nesting because container styles canât style the thing they query. Plus now the true selector is a combination of the nesting and whatâs in the container style query which is an awful brainbuster to keep track of.
This is not to say that Style Queries arenât useful. They totally are, and Iâm sure weâll uncover lots of cool use cases in the coming years. Iâm just saying that shoehorning them to behave exactly like mixins isnât great.
It sure would be nice if CSS got native mixins!
It would be yet another Sass feature making itâs way into the platform. In the case of mixins, it would be a great win, because the CSS would be more efficient than the way Sass had to express the mixin concept back in CSS. If you used a @mixin
10 times under different selectors, those styles blocks would be barfed out 10 duplicate times in the CSS. Perhaps not the worlds biggest deal thanks to file compression, but certainly not as efficient as the language itself just referring to a single block of styles.