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
Table of Contents
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.
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
Continer 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.
@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.