As someone just sittin’ back watching CSS evolve, it feels like we’re at one of the hottest moments of innovation in CSS history. It was really something when we got flexbox across all browsers, and not terribly long after, grid. They changed the game from CSS feeling like an awkward collection of tricks to something more sensible and of the times.
That feeling keeps getting more and more true all the time. Just in the last short while, here’s a list of things happening.
⚠️🤷 The syntax might not be exactly like any of the snippets below this when they ship for real. 🤷⚠️
Native Nesting
Native Nesting has become a First Public Working Draft, meaning it’s a lot closer to becoming a real thing than ever before. A lot of people use preprocessors just for the convenience of nesting and this should be helpful for those looking to simplify their build tools to avoid that.
I especially like how you can nest conditional rules.
.card {
& .title { }
& .body { }
@media (min-inline-size > 1000px) {
& { }
}
@nest body.dark & { }
}
I’ve heard whispers of this being a workable idea too, which avoids requiring the &
on simple selectors and also avoids @nest
at all.
.card {{
.title { }
.body { }
body.dark & { }
}}
Container Queries
Container Queries is just an Editor’s Draft (CSS Containment Module Level 3) at the moment, but they already have an implementation in Chrome (with a flag). These are a huge deal as they allow us to make styling decisions based on the size of the container itself, which in today’s component-driven world, is just a absolutely good idea.
- Miriam Suzanne: Container Query Proposal & Explainer
- Stephanie Eckles: A Primer On CSS Container Queries
- Geoff Graham: A Cornucopia of Container Queries
- Una Kravets: Next Gen CSS: @container
See the code for a simple example site (might look weird if you don’t have the flag on in Chrome).
/* Set containment on the parent you're querying */
.card-container {
/* Both work right now, not sure which is right */
contain: style layout inline-size;
container: inline-size;
}
.card {
display: flex;
}
@container (max-width: 500px) {
/* Must style a child, not the container */
.card {
flex-direction: column;
}
}
Container Units
Container Units have a draft spec as well, which, to me, nearly doubles the usefulness of container queries. The idea is that you have a unit that is based on the size of the container (width, height, or “inline-size” / “block-size”). I imagine the qi
unit is the most useful.
Hopefully soon, we’ll be writing container-scoped CSS that styles itself based on the size of itself and can pass that size for other properties to use inside. The font-size
property is an easy example of how useful this is (fonts that scale in size based on their container), but I’m sure container units will be used for all sorts of stuff, like gap
, padding
, and who knows what all else.
/* Set containment on the parent you're querying */
.card-container {
/* Both work right now, not sure which is right */
contain: style layout inline-size;
container: inline-size;
}
.card h2 {
font-size: 1.5rem; /* fallback */
}
@container type(inline-size) {
.card h2 {
font-size: clamp(14px, 1rem + 2qi, 56px)
}
}
Cascade Layers
Cascade Layers (in Working Draft spec) introduces a whole new paradigm for which CSS selectors win in the Cascade. Right now it’s mostly a specificity contest. Selectors with the highest specificity win, only losing out to inline styles and specific rules with !important
clauses. But with layers, any matching selector on a higher layer would win.
- Miriam Suzanne: Simple example/demo and an explainer document.
- Bramus Van Damme: The Future of CSS: Cascade Layers (CSS
@layer
)
@layer base;
@layer theme;
@layer utilities;
/* Reset styles with no layer (super low) */
* { box-sizing: border-box; }
@layer theme {
.card { background: var(--card-bg); }
}
@layer base {
/* Most styles? */
}
@layer utilities {
.no-margin { margin: 0; }
}
@when
Tab Atkins’ proposal for CSS When/Else Rules has been accepted and is a way of expressing @media
and @supports
queries in such a way that you can much more easily express else
conditions. While media queries already have some ability to do logic, doing mutually exclusive queries has long been hard to express and this makes it very simple.
@when media(width >= 400px) and media(pointer: fine) and supports(display: flex) {
/* A */
} @else supports(caret-color: pink) and supports(background: double-rainbow()) {
/* B */
} @else {
/* C */
}
Scoping
The idea of Scoped Styles (this one is an Editor’s Draft) is that it provides a syntax for writing a block of styles that only apply to a certain selector and within, but also have the ability to stop the scope, creating a scope donut.
My favorite part of all this is the “proximity” strength stuff. Miriam explains like this:
.light-theme a { color: purple; }
.dark-theme a { color: plum; }
<div class="dark-theme">
<a href="#">plum</a>
<div class="light-theme">
<a href="#">also plum???</a>
</div>
</div>
Good point right?! There is no great way to express that you want the proximity of that link to the .light-theme
to win. Right now, the fact that the specificity of both themes is the same, but .dark-theme
comes after — so it wins. Hopefully scoped styles helps with this angle, too.
@scope (.card) to (.content) {
:scope {
display: grid;
grid-template-columns: 50px 1fr;
}
img {
filter: grayscale(100%);
border-radius: 50%;
}
.content { ... }
}
/* Proximity help! */
@scope (.light-theme) {
a {
color: purple;
}
}
@scope (.dark-theme) {
a {
color: plum;
}
}
You can’t use anything on this list right now on your production websites. After all these years attempting to follow this kind of thing, I remain ignorant to how it all ultimately goes. I think the specs need to be finished and agreed upon first. Then browsers pick them up, hopefully more than one. And once they have, then I think the specs can be finalized?
I dunno. Maybe some of it will die. Maybe some of it will happen super fast, and some super slow. Likely, some of it will drop in some browsers but not others. Hey, at least we have evergreen browsers now so that when things do drop, it happens fast. I feel like right now we’re in a stage where most of the biggest and best CSS features are supported across all browsers, but with all this coming, we’ll be headed into a phase where support for the latest-and-greatest will be much more spotty.
It’s all looking so sassy. Excuse the pun.
@when is basically a toggle and/or multiple state function (as I see it).
I seem to remember Tab Atkins had a write-up about native toggle functionality in HTML (years ago) – I’d like to see that!
Regarding Container Units: On 10/07, the CSS Working Group has decided to use the
cq
prefix (instead of simplyq
before) for these units.Also worth noting, they 100% changed the strength of layered styles, making unlayered styles the strongest instead of weakest:
https://github.com/w3c/csswg-drafts/issues/6284#issuecomment-937262197
Protip: make your CSS-Tricks 120% better by reading everything Chris writes with Ted Lasso’s voice.
No way! Chris’s voice is way better!
I don’t know about you – but I’m still waiting for subgrid
They are so interesting…
But maybe it’s quicker to embed a sass compiler inside the browsers :P
Native CSS mixins?
Will they ever become a “thing”?
If not, then I’ll still be using Sass for a while yet. ;)
So much new stuff. And here I am still waiting for browsers to implement
attr
as specced ten years ago. I don’t think there’s another single CSS feature I have wanted to use as much but couldn’t.Scoping seems a good idea but the “proximity strength” use-case seems convoluted as CSS variables are a much better fit to solve it (and already work today).
All of these cool thingies would pale in front of a parent selector. God i wish there was a parent selector…
Another good reason for using Sass, with its
@at-root
directive, to “break out” of the current selector context to specify a parent selector.Kev, I think Zeno was referring to a selector that can style a parent based on its children. So like,
div:has(p)
would target thediv
by whether it has ap
as a descendent (it wouldn’t target thep
). Can’t do that even in Sass, since it’s essentially the direct inverse of how CSS currently works.All
@at-root
really does is undoes the nesting you may currently be in to specify a parent and then return to styling the child. LikeWhich CSS can do natively, you just don’t nest:
Until of course native nesting does come to CSS, in which case
@at-root
becomes@nest
.+1 on really wanting a parent selector, though. That combined with container queries would make component-based design far more intuitive in CSS.
The thing is, CSS is not like HTML and JavaScript.
Have you ever heard of something deprecated in CSS?