See also the video transcript and the slides
Intrinsic CSS with Container Queries & Units
CSS has always been contextual, with a Cascade & Normal Flow that allow content & styles to adapt across browsers, writing modes, device interfaces, and user-defined preferences. And Media Queries (part of the initial language proposal!) kicked off a decade of Responsive Web Design – with more explicit control over contextual adaptation. But with '@media', there's only one global context shared by every element on the page.
Container Queries are here to change that. We can define 'containers' that expose more localized context – and then query those containers in a variety of ways. Beyond just the highly anticipated size queries, we also get new container-relative units, and a roadmap for querying container styles, states, and more. Container queries & units have a lot to offer as we enter a more content-out era of Intrinsic Web Design, but they also come with some limitations of their own. Join Miriam to learn about how the feature works, how to start using it in production, and what to look forward to as Container Queries continue to evolve.See slides
Miriam is an author, artist, developer, and open web advocate. She’s
spent more than 15 years learning, teaching, and building web software
as a co-founder of OddBird,
Invited Expert with the W3C
CSS Working Group, and member of the Sass core team.
It's really wonderful to be here at my first TPAC.
Okay, well, okay, let's just dive right in.
Five minutes later.
Yeah, so some of you were there but when CERN released the first browser the HyperMedia Browser, it was designed for the next machine with this fancy futuristic graphic interface.
But you can't make a web that's worldwide by saying it works on my machine and everyone elses in edge case.
So right away they released the second browser, the Line Mode Browser without any graphics at all, no mouse just keyboard interactions, which is great.
And I wasn't paying a lot of attention at the time but it became the mission statement of this organization, right?
“Web for all.
Web on everything.” And everything is a lot of things that includes your latest 4K monitor and this whole list of mobile devices, assistive technology, smart watches, and all sorts of non-visual media.
The web is just getting on everything.
It's a mess and it's kind of sticky.
We're designing unknown content with unknown collaborators on an infinite and unknowable canvas across operating systems, interfaces, writing modes, language.
This is absurd that we're even trying to do this and amazing that we've pulled it off to some extent.
And a lot of that is thanks to normal flow which predates CSS entirely and allows us to have content that reflows to a container and containers that grow based on the content.
And that's a pretty wild idea to be able to do that.
And yeah, fast forward a bit around 2008, 2009, we started to get media queries which led us take this responsiveness and actually measure things intentionally and then make changes based on the measurements we get.
We can measure the viewport and we can find out things and we can change and explicitly adapt to those differences in context.
And that's pretty cool.
Ethan Marcotte gave us a name for it, Responsive Web Design and a few rules to follow to do this new thing.
That was an evolution of an already responsive web.
But if we pull out the intrinsic sides of everything, we can have fluid percentage based, grids flexible percentage based images and then use media queries to make it all fit together.
And that's pretty cool.
And right away, everybody who thought, well, what about container queries?
Wouldn't it be cool if we could measure container queries?
I mean it would be nice if we could have a component and we could put it in a sidebar and it would lay out differently for the sidebar.
But no, that's not possible, it's never gonna happen.
And the reason it's not gonna happen is because of normal flow.
It's because of that very cool thing where the box sizes the content and the content sizes the box, what are you gonna measure?
What are you gonna change?
How's it gonna work?
It doesn't work.
Fast forward again, another decade and everything is changed again.
We've got Flexbox, Grid, Box Alignment, Viewport Units, Intrinsic Sizing, Aspect Ratios, Min, Max and Clamp.
Everything has fundamentally changed about how we do web design in the last decade.
As Jen Simmons points out, we can now do truly two dimensional layouts.
We can combine fluid with flexible.
We don't have to pull out all the intrinsic sizes.
We get all these different stages of squishiness - which is a great phrase - nested context that expand and contract their content with media queries maybe as needed.
They're no longer the basis of everything.
And she's calling this Intrinsic Web Design and I think that makes sense.
And it's another evolution of the same responsive web.
It's still responsive and intrinsic, we're building on something.
But wait, nested context with content that expands and contracts, that sounds like we're back to that same problem, right?
What if we've got these different contexts now and we want to be able to measure them and respond to them somehow and they nest and why can't we ask these questions?
And over the last decade, there have been a lot of people laying the groundwork.
There has been so much work done on this problem to make it possible.
And in 2020, David Baron and Brian Kardell both put forward really interesting proposals of how we might move forward with something like this.
And both of those are worth looking into.
We focused on '@container' as we thought it gave us the most win right away.
And it's built on CSS containment, which is one of those foundations that's been laid over the last 10 years which allows us to isolate particular aspects of an element and its children so that we don't get those layout loops.
But CSS containment size containment specifically removes intrinsic sizing from the container.
In order to break the loop of content sizing the container and container sizing the contents, we turn off that flow one direction, the contents can no longer size the container.
And that's pretty dangerous as we know.
When you've got a container that is ignoring its contents we can can run into problems and sure we can try to solve it maybe with `overflow: wrap` but we've also set a `height` here that's not gonna quite work.
And if we add containment we're just making the problem worse.
So that's not gonna work great.
What we really need is 1D containment, a way to say, okay, we'll contain it in one axis and we'll let it flow in the other axis, but there's even more problems here.
And up until last summer, we still didn't know if this was gonna work.
There were still a lot of problems.
We had a working prototype and we thought it might not work, which was interesting.
But we did finally find a way through, Ian Kilpatrick pointed out that we had actually solved a lot of these problems for floats.
You can dig into the details somewhere else.
I only have a few minutes.
So we got `contain: inline-size`.
We are not able to do `contain: block size` that had more problems.
So for now we get single access containment but only in a single access, right?
We also have size containment on both axis if we want it, but you have to be more careful with it.
And everybody agreed this was a path forward and we can ship it.
So for how it works?
The first thing that we need to do is define our containers because we have to do this somewhat invasive process of turning off intrinsic sizing.
So we could do that with the `contain` property but it's not entirely clear.
I just want to query something, I don't want to remember what are all the containments that are required in order for that to work.
So there's a new syntax `container-type: inline-size`.
We can also say container type size or normal is the default.
Those are the three there right now.
So we can give it a container type of inline size and the browser will apply the necessary containment.
So we just say, what do I want to query?
I want to query the inline size.
And there's a few caveats.
Like I mentioned, when we add this containment.
The inline size is no longer intrinsic so we're gonna have to give our element an inline size from the grid track or auto sizing in block layout or whatever extrinsic sort of sizing we can give it.
Subgrids won't be able to contribute back to grandparent grids.
Counters will be contained.
So there's a few things.
We wanna be careful about where we put these but we can do it.
Once we have containers, we can query them.
And there's no default container but none of these caveats are particularly a problem on the root element.
So we can create for ourselves a default container.
And at that point we can basically take our media queries, change app media to app container and we have a container query.
And this might be worth doing with all media queries as soon as you want.
Every element that's matched inside of a container query is going to go looking for its nearest container ancestor with the appropriate container type.
And em-based queries, like we said, `min-width: 40em` in that query, will resolve against the actual computed font size of the container rather than the browser `em` in theory.
So right away we get an advantage just switching over just for the root element.
There's no self querying, if that makes sense.
The container has to be outside of the element that is querying but containers can query other larger containers.
So we can do all sorts of nesting and have every container respond to the container above it.
We can also give containers names which is useful if we ever want to jump containers because by default we're always getting the nearest one.
We figured by default you usually want the nearest one.
But it might be useful to be able to say, okay, I've got different types of containers I want to jump between them.
So there's a `container-name` property, you can give it as many names as you want.
You can think of these like classes.
So you could give it a specific name that's like well the sidebar, there's only one of those but we'll give it a name.
And then we'll also have a pattern of this is part of the layout and we'll have component ones too or something.
We can give it all sorts of names and then we can add a name to our query.
And now that changes the logic slightly.
We're looking for the nearest ancestor container with the correct name and the correct type.
So yeah, for each matched element, looks for its nearest ancestor that has the required container name and the required container type.
There's a shorthand for this.
It's really only for when you want both.
So the name comes first and is required.
So you can say container, name, sidebar or layout and it's an inline size container.
And that works here.
So we can add a sidebar there.
We've got the same element, the same card and we add container queries.
And now the one in the sidebar works.
Yeah, so you can see the cards there and we define some containers somewhere in here.
There's our fallback and then the app container.
So you can see on a small screen without the sidebar yet there's the sidebar, there's the container queries.
One of the first demos that was created was this one by my colleague David Herron, who just showed the same block quote in three different areas of the page growing in different ways.
And then Jay, who also did one of the demos from Greg's talk, made this.
Oh, there we go.
That's just resizing the container there, which is fun.
Yuni Kravitz made this one to show responsive icons in a container.
I suppose it's strange that I'm changing the entire browser there not the container but you can see it in context in a card.
So not only the button changing based on the size of the container but also the icon inside of it changes at different sizes.
There's a lot that we can do with this.
There's a little bit of a problem where we're measuring the size of an actual element and some of the things we want to measure aren't elements.
We wanna measure a grid track or sizing in a flex box.
And so for this we need components that carry their container with them.
So Vancouver Mud Turtles come into play, so we can do that.
These containers have a wrapper around them, the div of card, and then query the card that is surrounding each card.
So each one carries its container with it.
Yeah, so we can see them change sizes and then if one is taking up more space than the others it might have a different layout.
And we can do the same thing with grid track sizing.
This one is a demo by Max Bock but I guess I need to zoom very out.
There we go.
So a bookstore.
These are all custom elements, web components, whoop.
Yeah, here we go.
If I drag a web component from one place to another, the same web component takes on a different style using a container query.
Again, it's using the host of the web component as the container.
So it's just on colon host, I won't go looking for it.
I will move on.
We also get container query units, which are the same idea as viewport units, but with a 'cq' instead of a 'v'.
So container query width, height in line or block size and then min or max.
And these do get a default container.
They'll respond to the small viewport if no container is present so that they always work.
And we can see these have the same font sizing, but in different size containers.
Scott Cullum made an even better demo of that with these ads that change size.
Oh, it's not giving me an adjustment there, but yes, can be used for typography or something like that.
So we went from, it's impossible to its shipping, to oh it shipped in two browser engines in the last two weeks.
Safari landed yesterday, so it just shipped.
And Mozilla is working on it.
There's also a polyfill that you can use, works pretty well.
And also you can check for support of the properties if you want to use '@supports' for fallbacks.
We also have a spec for style queries.
We realized similar to media queries where you might want to query more than just the size of the media, you want to query media preferences or device interface, capabilities.
The same is true of elements.
What if we could style query styles.
So we have a spec for it.
You could say container style has the custom property colors set to invert and we could do something that responds to that.
One of the common examples really simplified is I've got my `em`s are font style of italic so are my block quotes.
But I want `em`s that are inside of something that's already italic to be emphasized in a different way.
Chrome actually released a prototype of this just recently in Chrome Canary.
So I spent the afternoon building a demo.
So here we've got a default button and a container query that accepts a couple different button names.
So I can change one custom property button theme mia and we get the mia themed button, that's me.
And then we can say, we want actually mia-plus here.
So that works, go play with it.
We would love to see more use cases for style queries and try to get those shipped everywhere as well.
We think there's probably other questions that we have for containers.
We're looking at state queries.
Could we query whether a container is positioned sticky and currently stuck?
We haven't fleshed this out, we haven't specked it yet but we think there might be more opportunities for asking questions of containers.
If you have ideas, there's a link in the slides to the issue.
Our medium is not done.
Our medium is still going through radical changes and that's why we're all here.