DEV Community

Diana Le
Diana Le

Posted on

CSS Layouts: Get Started with Container Queries

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

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);
  }
}


Enter fullscreen mode Exit fullscreen mode

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:

Row of 3 cards

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:

Layout featuring a left sidebar and 3 cards on the right

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);
  }
}


Enter fullscreen mode Exit fullscreen mode

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:

Codepen screenshot showing a card component being used 3 times on the same page in different sections

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>


Enter fullscreen mode Exit fullscreen mode

The card-wrapper class is where the grid columns are set:



.card-wrapper {
  display: grid;
}


Enter fullscreen mode Exit fullscreen mode

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>


Enter fullscreen mode Exit fullscreen mode

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 (use inline-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);
  }
}


Enter fullscreen mode Exit fullscreen mode

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;
  }
}


Enter fullscreen mode Exit fullscreen mode

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)

Collapse
 
lexiebkm profile image
Alexander B.K.

To make the target container even clearer, I would add container-name property in the rule like so :

/* Create the container / set the containment context */
.card-wrapper-container {
  container-type: inline-size;
  container-name: anyvalidname;
  /* or using shorthand syntax like the following :
     container: anyvalidname / 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 anyvalidname (min-width: 560px) {
  .card-wrapper {
    grid-template-columns: repeat(2, 1fr);
  }
}
Enter fullscreen mode Exit fullscreen mode

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.

Collapse
 
dianale profile image
Diana Le

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!