Chrome is experimenting with @container
, a property within the CSS Working Group Containment Level 3 spec being championed by Miriam Suzanne of Oddbird, and a group of engineers across the web platform. @container
brings us the ability to style elements based on the size of their parent container.
You can think of these like a media query (@media
), but instead of relying on the viewport to adjust styles, the parent container of the element you’re targeting can adjust those styles.
Container queries will be the single biggest change in web styling since CSS3, altering our perspective of what “responsive design” means.
No longer will the viewport and user agent be the only targets we have to create responsive layout and UI styles. With container queries, elements will be able to target their own parents and apply their own styles accordingly. This means that the same element that lives in the sidebar, body, or hero could look completely different based on its available size and dynamics.
@container
in action
In this example, I’m using two cards within a parent with the following markup:
<div class="card-container">
<div class="card">
<figure> ... </figure>
<div>
<div class="meta">
<h2>...</h2>
<span class="time">...</span>
</div>
<div class="notes">
<p class="desc">...</p>
<div class="links">...</div>
</div>
<button>...</button>
</div>
</div>
</div>
Then, I’m setting containment (the container-type
property) on the parent on which I’ll be querying the container styles (.card-container
). I’m also setting a relative grid layout on the parent of .card-container
, so its inline-size
will change based on that grid. This is what I’m querying for with @container
:
.card-container {
container-type: inline-size;
width: 100%;
}
Now, I can query for container styles to adjust styles! This is very similar to how you would set styles using width-based media queries, using max-width
to set styles when an element is smaller than a certain size, and min-width
when it is larger.
/* when the parent container is smaller than 850px,
remove the .links div and decrease the font size on
the episode time marker */
@container (max-width: 850px) {
.links {
display: none;
}
.time {
font-size: 1.25rem;
}
/* ... */
}
/* when the parent container is smaller than 650px,
decrease the .card element's grid gap to 1rem */
@container (max-width: 650px) {
.card {
gap: 1rem;
}
/* ... */
}
Container Queries + Media Queries
One of the best features of container queries is the ability to separate micro layouts from macro layouts. You can style individual elements with container queries, creating nuanced micro layouts, and style entire page layouts with media queries, the macro layout. This creates a new level of control that enables even more responsive interfaces.
Here’s another example that shows the power of using media queries for macro layout (i.e. the calendar going from single-panel to multi-panel), and micro layout (i.e. the date layout/size and event margins/size shifting), to create a beautiful orchestra of queries.
Container Queries + CSS Grid
One of my personal favorite ways to see the impact of container queries is to see how they work within a grid. Take the following example of a plant commerce UI:
No media queries are used on this website at all. Instead, we are only using container queries along with CSS grid to display the shopping card component in different views.
In the product grid, the layout is created with grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
. This creates a layout that tells the cards to take up the available fractional space until they hit 230px
in size, and then to flow to the next row. Check out more grid tricks at 1linelayouts.com.
Then, we have a container query that styles the cards to take on a vertical block layout when they are less than 350px
wide, and shifts to a horizontal inline layout by applying display: flex
(which has an inline flow by default).
@container (min-width: 350px) {
.product-container {
padding: 0.5rem 0 0;
display: flex;
}
/* ... */
}
This means that each card owns its own responsive styling. This yet another example of where you can create a macro layout with the product grid, and a micro layout with the product cards. Pretty cool!
Usage
In order to use @container
, you first need to create a parent element that has containment. In order to do so, you’ll need to set contain: layout inline-size
on the parent. You can use inline-size
since we currently can only apply container queries to the inline axis. This prevents your layout from breaking in the block direction.
Setting contain: layout inline-size
creates a new containing block and new block formatting context, letting the browser separate it from the rest of the layout. Now, we can query!
Limitations
Currently, you cannot use height-based container queries, using only the block axis. In order to make grid children work with @container
, you’ll need to add a wrapper element. Despite this, adding a wrapper lets you still get the effects you want.
Try it out
You can experiment with the @container
property in Chromium today, by navigating to: chrome://flags
in Chrome Canary and turning on the #experimental-container-queries flag.
Can you query multiple containers?
I don’t think so, but I’m just wrapping my brain around all this. I believe in the current spec you don’t specificity even one container. It just assumes you mean the parent element of whatever it is you select inside.
Yes! You can set containment on more than one parent, and query against those parents.
So we write queries within
@container
which references the single parent container with thecontain
property specified. How would this work if you wanted multiple containers?Any elements that are targeted by a selector inside the
@container
will query their nearest ancestor container. So if you set up both.sidebar
andmain
as containers, you can put elements inside either one, and each element will query the container that it is in. You can nest containers as much as you like, and elements will always query the closest one.It feels so weird to think this is finally coming true.
Many thanks to those involved who made this possible!
Container Queries, Grid, and Web Components, there’s no stopping us now (shut up Safari!)
Cool! Any way to have different containers? Or would you need to do something like reference the parent in the selector?
Yes! You can set containment on more than one parent, and query against those parents.
Hope to see this finalised soon, it is the holy grail to making UI components reusable.
I wonder how far is it from introducing this in the official Chrome version
It will be so great to have this as native CSS feature!
Until then there luckily are libs :)
In this demo for example there are many container queries:
https://tobireif.com/demos/grid/
It uses the lib https://github.com/eqcss/eqcss/ and its syntax.
very cool Tobi
So it’s more or less a CSS equivalent of the resize observer – something I hoped for a long time ago!
The polyfill uses resize observer :) https://github.com/jsxtools/cqfill
Hey. The limitation about adding @container to a grid element. Is this going to be something we’ll have to work around in the shipping version of Container Queries or will there be a way to make a grid element be a container without having to add a wrapping element?
Thanks!
This sounds like a trick I can use for a cemetery website using the grid for individual plots and using query to change the amount and type of information about who is buried in that lot depending on size. Exciting!
I assume this is going to sit in experimental for years like backdrop-filter. Something we really want, that probably works perfectly fine already, but we won’t see for years.
I hope I’m wrong but I’d slap down $100 on it not happening for at least a year.