Modern CSS keeps giving us a lot of new, easier ways to solve old problems, but often the new features we’re getting don’t only solve old problems, they open up new possibilities as well.
Container queries are one of those things that open up new possibilities, but because they look a lot like the old way of doing things with media queries, our first instinct is to use them in the same way, or at least a very similar way.
When we do that, though, we aren’t taking advantage of how “smart” container queries are when compared to media queries!
Because of how important media queries were for ushering in the era of responsive web design I don’t want to say anything mean about them… but media queries are dumb. Not dumb in terms of the concept, but dumb in that they don’t know very much. In fact, most people assume that they know more than they do.
Let’s use this simple example to illustrate what I mean:
html { font-size: 32px;}body { background: lightsalmon;}@media (min-width: 35rem) { body { background: lightseagreen; }}
What would the viewport size be for the background color to change? If you said 1120px wide — which is the product of multiplying 35 by 32 for those who didn’t bother doing the math — you aren’t alone in that guess, but you’d also be wrong.
Remember when I said that media queries don’t know very much? There are only two things they do know:
And when I say the browser’s font size, I don’t mean the root font size in your document, which is why 1120px in the above example was wrong.
The font size they look at is the initial font size coming from the browser before any values, including the user agent styles, are applied. By default, that’s 16px, though users can change that in their browser settings.
And yes, this is on purpose. The media query specification says:
Relative length units in media queries are based on the initial value, which means that units are never based on results of declarations.
This might seem like a strange decision, but if it didn’t work that way, what would happen if we did this:
html { font-size: 16px;}@media (min-width: 30rem) { html { font-size: 32px; }}
If the media query looked at the root font-size (like most assume it does), you’d run into a loop when the viewport would get to 480px wide, where the font-size would go up in size, then back down over and over again.
Container queries are a lot smarterWhile media queries have this limitation, and for good reason, container queries don’t have to worry about this type of problem and that opens up a lot of interesting possibilities!
For example, let’s say we have a grid that should be stacked at smaller sizes, but three columns at larger sizes. With media queries, we sort of have to magic number our way to the exact point where this should happen. Using a container query, we can determine the minimum size we want a column to be, and it’ll always work because we’re looking at the container size.
That means we don’t need a magic number for the breakpoint. If I want three columns with a minimum size of 300px, I know I can have three columns when the container is 900px wide. If I did that with a media query, it wouldn’t work, because when my viewport is 900px wide, my container is, more often than not, smaller than that.
But even better, we can use any unit we want as well, because container queries, unlike media queries, can look at the font size of the container itself.
To me, ch is perfect for this sort of thing. Using ch I can say “when I have enough room for each column to be a minimum of 30 characters wide, I want three columns.”
We can do the math ourselves here like this:
.grid-parent { container-type: inline-size; }.grid { display: grid; gap: 1rem; @container (width > 90ch) { grid-template-columns: repeat(3, 1fr); }}
And this does work pretty well, as you can see in this example.
CodePen Embed FallbackAs another bonus, thanks to Miriam Suzanne, I recently learned that you can include calc() inside media and container queries, so instead of doing the math yourself, you can include it like this: @container (width > calc(30ch * 3)) as you can see in this example:
CodePen Embed FallbackA more practical use caseOne of the annoying things about using container queries is having to have a defined container. A container cannot query itself, so we need an extra wrapper above the element we want to select with a container query. You can see in the examples above that I needed a container on the outside of my grid for this to work.
Even more annoying is when you want grid or flex children to change their layout depending on how much space they have, only to realize that this doesn’t really work if the parent is the container. Instead of having that grid or flex container be the defined container, we end up having to wrap each grid or flex item in a container like this:
```