I haven’t been more excited for a CSS feature like I’m now in the past six years I spent as a front-end developer. The prototype of container queries is now available behind a flag in Chrome Canary. Thanks to efforts from smart people like Miriam Suzanne and other folks.

I remember seeing a lot of jokes about the support for CSS container queries, but they are finally there. In this article, I will walk you through why we need container queries, how they will make your life easier, and most importantly, you will achieve more powerful components and layouts.

If you’re excited as I am, let’s dig in. Are you ready?

The problem with CSS media queries

A web page consists of different sections and components, and we make them responsive by using CSS media queries. There is nothing wrong with that, but it has limitations. For example, we can use a media query to show the minimal version of a component on mobile versus desktop.

Oftentimes, responsive web design is not about the viewport or the screen size. It’s about the container size. Consider the following example:

We have a very typical layout with a card component. It has two variations:

There are multiple ways for implementing that in CSS, but the most common one is like the following. We need to create a base component, and then make variations of it.

.c-article {
  /* The default, stacked version */
}

.c-article > * + * {
  margin-top: 1rem;
}

/* The horizontal version */
@media (min-width: 46rem) {
  .c-article--horizontal {
    display: flex;
    flex-wrap: wrap;
  }

  .c-article > * + * {
    margin-top: 0;
  }

  .c-article__thumb {
    margin-right: 1rem;
  }
}

Notice that we created the class .c-article--horizontal to handle the horizontal version of the component. If the viewport width is greater than 46rem, then the component should switch to the horizontal variation.

This isn’t bad, but somehow it makes me feels a bit limited. I want the component to respond to its parent width, not the browser viewport or screen size.

Consider that we want to use the default .c-card in the main section. What will happen? Well, the card will expand to the width of its parent and thus it will be too large. See the following figure:

This is a problem, and we can solve it with CSS container queries (Yay, finally). Before diving into them, let me give you a glimpse of the result we want.

We need to tell the component that if its direct parent width is greater than 400px, then it needs to switch to the horizontal style. Here is how the CSS will look like:

<div class="o-grid">
  <div class="o-grid__item">
    <article class="c-article">
      
    article>
  div>
  <div class="o-grid__item">
    <article class="c-article">
      
    article>
  div>
div>
.o-grid__item {
  container-type: inline-size;
}

.c-article {
  /* The default style */
}

@container (min-width: 400px) {
  .c-article {
    /* The styles that will make the article horizontal**
        ** instead of a card style.. */
  }
}

How CSS container queries will help us?

Warning: the CSS container queries are only supported in Chrome Canary under an experiment flag for now.

With CSS container queries, we can solve the above problem and make a fluid component. That means, we can throw the component in a narrow parent and it will turn into the stacked version, or to throw it in a wide one and it will turn into the horizontal version. Again, All of them that is independent of the viewport width.

Here is how I imagine it.

The purple outline represents the parent width. Notice how when it gets bigger, the component adapts to that. Isn’t that awesome? This is the power of CSS container queries.

How container queries works

We can now experiment with container queries Chrome canary. To enable it, go to chrome://flags and search for “container queries”, then enable it.

The first step is to add the container-type property. Since a component will adapt based on its parent width, we need to tell the browser to only repaint the affected area, not the whole page. With the container-type property, we can let the browser know about that ahead of time.

The value inline-size means to respond to the parent’s width changes only.

<div class="o-grid">
  <div class="o-grid__item">
    <article class="c-article">
      
    article>
  div>
  <div class="o-grid__item">
    <article class="c-article">
      
    article>
  div>
  
div>
.o-grid__item {
  container-type: inline-size;
}

This is the first step. We defined the element .o-grid__item as a containment parent for the .c-article within it.

The next step is to add the styles we want to make container queries work.

.o-grid__item {
  container-type: inline-size;
}

@container (min-width: 400px) {
  .c-article {
    display: flex;
    flex-wrap: wrap;
  }

  /* other CSS.. */
}

The @container is the .o-grid__item element, and the min-width: 400px is the width of it. We can even take it further and add more styles. Here is a video of what can be achieved for the card component:

The styles we have in there are:

  1. The default, a card-like look.
  2. A horizontal card with a small thumbnail.
  3. A horizontal card with a large thumbnail.
  4. If the parent is too large, the style will a hero-like to indicate that it’s a featured article.

Let’s dig into use cases for CSS container queries.

Use cases for CSS container queries

Container queries and CSS grid auto-fit

For some cases, using auto-fit in CSS grid can lead to unexpected results. For example, a component will be too wide and its content is hard to read.

To give you a bit of context, here is a visual that shows the difference between auto-fit and auto-fill in CSS grid.

Notice that when auto-fit is used, the items expanded to fill the available space. However, in case of auto-fill, the grid items won’t grow and we will have a free space instead (The dotted item on the far right).

You might be thinking now, what this has to do with CSS container queries? Well, each grid item is a container, and when it’s expanded (AKA we’re using auto-fit), we need the component to change based on that.

<div class="o-grid">
  <div class="o-grid__item">
    <article class="c-article">article>
  div>
  <div class="o-grid__item">
    <article class="c-article">article>
  div>
  <div class="o-grid__item">
    <article class="c-article">article>
  div>
  <div class="o-grid__item">
    <article class="c-article">article>
  div>
div>
.o-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  grid-gap: 1rem;
}

When we have four elements, the result should look something like this.

That will change when the number of articles is less, here is what will happen. The less item we have, the wider they will become. The reason is that auto-fit is used. The first one looks good, but the last two (2 per row, 1 per row) doesn’t look good as they’re too wide.

What if each article component changes based on its parent width? That way, we can get the benefit of auto-fit very well. Here is what we need to do:

If the grid item width is greater than 400px, then the article should switch to the horizontal style.

Here is how we can do this:

.o-grid__item {
  container-type: inline-size;
}

@container (min-width: 400px) {
  .c-article {
    display: flex;
    flex-wrap: wrap;
  }
}

Also, we want to display the article with a hero section if it’s the only item in the grid.

.o-grid__item {
  container-type: inline-size;
}

@container (min-width: 700px) {
  .c-article {
    display: flex;
    justify-content: center;
    align-items: center;
    min-height: 350px;
  }

  .card__thumb {
    position: absolute;
    left: 0;
    top: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
  }
}

That’s it. We have a component that responds to its parent width, and it can work under any context. Isn’t that awesome?

Check out the demo on CodePen.

Oftentimes, we need to tweak a component to make it work in small width containers like an