Welcome to another year of the State of CSS. Like for last year's State of CSS 2022, I will be creating a series of examples and tutorials mentioned in the survey so we can all learn and keep up with the latest features.
Container Queries
Let's kick off this series with an introduction to container queries - a new alternative to media queries. I heard about container queries a few years ago, and went through some articles and video tutorials, but for some reason with the examples I reviewed, I just couldn't get it. Everyone was saying how this would dramatically change CSS, but it was hard for me to grasp by how much. After finally understanding the basics I realized that container queries are super-powered media queries. (But be aware they do not replace all media queries).
- When Media Queries Become Inefficient
- Enter the Container
- How to Set Up Container Queries
- When to Keep Using Media Queries
When Media Queries Become Inefficient
This year I've been working more with components, and when I was working on a project with a permanent sidebar and needed to style the content within the remaining layout portion, I realized this would finally be the opportunity to use container queries. A media query would not work well unless I always factored in the size of the sidebar, which was not going to be efficient.
Why doesn't this work well with media queries? Imagine that you want to display cards, and for screens 1200 pixels and above, you would like to display a single row of 3 cards:
@media (min-width: 1200px) {
.cards-wrapper {
display: grid;
grid-template-columns: repeat(3, 1fr);
}
}
For a layout that is full-width, this works fine with media queries because the width of the browser is the same as the width of the container:
What if the layout has a left sidebar? The browser width is still 1200 pixels but the actual container that the cards are displayed in is now only about 800 pixels. But based on the same media query, the cards would still display a single row of 3 cards:
If you wanted to change the number of cards per row to 2 instead of 3 on this specific layout, you'd have to either add a class to this specific instance of cards or use a compound selector:
@media (min-width: 1200px) {
.two-column-right .cards-wrapper {
grid-template-columns: repeat(2, 1fr);
}
}
This will work but makes the code not as clean because you have to consider all locations where these specific cards are used and adjust all places if the styling changes.
Enter the Container
What if we could style our cards based on the parent container so that regardless of what the layout is, the cards would be styled correctly? That is the power of container queries. We could style those same cards for the exact dimensions of the container, and then afterwards we wouldn't have to worry about where the card component is used because we've already accounted for the styling. We basically have a component with built-in styles that can slot anywhere.
Whether it's dropped into a full-width layout or into a sidebar, once we have the CSS rules in place, we don't have to worry about creating custom rules for a specific layout. We can cover all the scenarios in the same set of rules; the styles are tied to that container component.
Here's an example of the same card component being used 3 different times on a page:
Notice that the cards are styled differently between: the section "Container Queries", the "Sidebar", and at the bottom of the page.
You get the best of both worlds with container queries. If your container is always the same width as the browser, then it still works. Container queries still give you the benefits of media queries but with even more functionality.
How to Set up Container Queries
Let's walk through the previous example screenshot taken from the following Codepen:
1. Determine the container
This can be confusing at first. You need to set the container on the parent of the element that you later want to style with container queries. For example, the structure of the cards is:
<!-- Card wrapper -->
<div class="card-wrapper">
<!-- Individual cards -->
<article class="card">
...
</article>
<article class="card">
...
</article>
</div>
The card-wrapper
class is where the grid columns are set:
.card-wrapper {
display: grid;
}
You might assume that since this .card-wrapper
is the container for the individual cards, that you would set the container property here, but that would be incorrect. You need to create another wrapper as the parent, in this example .card-wrapper-container
, because we want to style .card-wrapper
(and how many cards to display per row) based on the width of .card-wrapper-container
:
<!-- New wrapper -->
<div class="card-wrapper-container">
<div class="card-wrapper">
<article class="card">
...
</article>
</div>
</div>
2. Set the CSS Container Rules
Now that we know what our container is, we need to:
- Create the container by setting the
container-type
(useinline-size
as the value for now since this probably covers most of the use cases) - Define the container and create styling rules based on the width of the container. This logic is incredibly similar to media queries:
/* Create the container / set the containment context */
.card-wrapper-container {
container-type: inline-size;
}
/* Define the container query using @container */
/* The rule below says: find the closest ancestor with a
containment context
- in this case "card-wrapper-container" -
and when the width is 560 pixels and above,
set the grid columns to 2 */
@container (min-width: 560px) {
.card-wrapper {
grid-template-columns: repeat(2, 1fr);
}
}
Those are the minimum CSS rules you need for setting up container queries. You can style anything within the container such as setting font sizes:
/* Change the grid columns to 3 per row
once the width of the container is 768px
and adjust the font size of the card title */
@container (min-width: 768px) {
.card-wrapper {
grid-template-columns: repeat(3, 1fr);
}
.card__title {
font-size: 1.5rem;
}
}
And voilà, you have set up container queries and changed the grid columns and font sizes within. You can keep adding more breakpoints and then place the .card-wrapper-container
HTML anywhere onto a page (and in multiple locations), and it will always style based on the width of the container. 🥳
When to Keep Using Media Queries
Media queries are still useful for setting the overall layout of the page, and needed for accessibility or browser preferences like prefers-reduced-motion
or prefers-color-scheme
. If you need to support older browsers, then media queries are also more browser-compatible. But container queries can be a powerful, more efficient replacement for media queries when you want to break down your site into reusable components. Hopefully this article gives you some ideas that you can use as you get started with this new feature.
Top comments (2)
To make the target container even clearer, I would add
container-name
property in the rule like so :Actually I am still learning; I am currently still exploring Grid more detail. But when I saw this post, I felt that I wanted to take time to read, because this was relevant to what I was currently learning, including all @ rules which can be useful for my use cases in the future.
My current priority of @ rules is on @media, @supports, @import, @layer and @keyframes.
I haven't been paying attention to this @container yet. But now that you introduce it here, I think I will take time to explore it more as well as the related topics like containment via
contain
property.So thank you for introducing another useful @ rule here.
Yes, that's a good point about container names. I'm thinking to do a more advanced post about container queries later on after I work with them some more. I haven't used multiple containment contexts yet.
Glad that you're learning these topics!