Join Our Newsletter!

Keep up to date with our latest blog posts, new widgets and features, and the Common Ninja Developer Platform.

How To Create Responsive Web Design Using CSS Container Queries

Sergei Davidov,
How To Create Responsive Web Design Using CSS Container Queries

Responsive web design is important to the success of any website. Accessing websites through mobile devices is an ever-rising trend, and, as a web developer or web designer, you have to keep up with the trend.

Traditionally, creating a responsive web design involves using media queries — a practice that’s been around for some time now.  

The CSS container query is like a media query, but for containers. You can use this CSS feature to style an element differently based on its parent or container size. 

This article will look at an existing problem with media queries and how CSS container queries can help you solve it.

Prerequisites

Basic knowledge of CSS

What Are CSS Container Queries?

Container queries, a brand-new CSS feature, are causing quite a stir in the web design world. As part of a broader movement in the design ecosystem, they enabled websites to move away from designs focused on page size and toward a container-based approach.

Based on the parent container, containers let you add styles to elements such as font sizes, colors, and layout widths. This is a new method of responsive design that previously depended on viewport size.

For instance, an element will change sizes on a website based on the container, not the viewport, if the element is in a hero section or a grid on a page.
Container queries like media queries use breakpoints. The primary difference is that whereas @media responds to the viewport and user agent, @container responds to the parent container.

What Are Media Queries?

Media queries is a CSS 3 feature that enables content rendering to adjust to different conditions, such as screen resolution (e.g., mobile and desktop screen size).

Media queries come in handy when you want to modify your website or app based on a device’s general type (such as print vs. screen) or particular attributes and parameters (such as screen resolution or browser viewport width).

Media Queries (the Problem)

As mentioned earlier in this article, this article aims to discuss a problem you might encounter when making specific components responsive using media queries.

To get started, take a look at our demo project for this section in this Codepen. This is what it looks like below:

Here, we have a typical blog layout with a sidebar to the left (Aside) and the main area to the right (Main). Let’s say we want to display the widget of the element with the author’s biography in both places. But we want the component in the sidebar to look the way it is in the image above, while the one in the main area on the right takes on a sort of square shape rather than the rectangular shape we have here.  

Basically, when we have more space in the main area, we want to use the square-like version of our main area component, and when we have less space, we want to use the sidebar version on the left because it works better on or in areas with smaller widths. 

So how do we do this? 

Well, the easiest thing to do would be to create two versions of our components in CSS using a class, as shown in our HTML code below:

<main> <div class="about large"> <header> <img class="avatar" src="https://source.unsplash.com/7YVZYZeITc8/240x240" alt="Portrait"> <h1>Chisom Uma</h1> </header> <p>I'm a big lover of tech and that's why I write mostly about that. But I'm also passionate about travel and lifestyle.</p> <ul class="social-icons"> <li><a href=""><i class="fab fa-twitter"></i></a></li> <li><a href=""><i class="fab fa-facebook-f"></i></a></li> <li><a href=""><i class="fab fa-linkedin-in"></i></a></li> </ul> </div> </main>

Here, we created a class of large that we applied to our main area. This is the result below:

It looks good so far, right?

If we resize our viewport, we will still get the larger version shown in the image above. But how do we portray this larger version only when there’s enough room for it? The answer is media queries. But how do we do this with media queries?

Well, with media queries, we can’t remove a class from an element unless there’s some JavaScript involved, which would complicate things. We could eliminate our second class approach and write a media query where we target the essential elements and change them, but it will also change both elements. 

We could also want to target just the elements in the aside or the main, which is correct. Still, even if we target the element in the main, it won’t solve our problem because we might have a particular screen size where the viewport or the media query will kick in and show us either the small or more significant version.

What if, in situations where we want to show a larger version of our project even on smaller screens when there’s room? How do we do that?

These are the problems because when we use media queries, we are tied to the viewport width

What if we want our component to change its style based on the size or, in our case, the width of its parent element? Wouldn’t that be better and easier to manage? Well, in our case, we have the same element in both places, but how can we make the same component be displayed differently on the side, which is smaller in size, or on the main element, which is much larger in size? We can’t do that with media queries, or maybe we can, but it will take a lot of effort. So, that’s the problem, and media queries can’t exactly help us. But, container queries can help us and even solve the problem entirely.

Defining a Container Query

In this section will look at how you can solve the problem using container queries. To get started with this section, here is the link to the complete Codepen

The container query looks at the size of a container or a parent element to determine what changes need to be made. 

Let’s take a look at our HTML code below:

<aside> <div class="container"> <div class="about"> <header> <img class="avatar" src="https://source.unsplash.com/7YVZYZeITc8/240x240" alt="Portrait"> <h1>Chisom Uma</h1> </header> <p>I'm a big lover of tech and that's why I write mostly about that for Commonninja. But I'm also passionate about travel and lifestyle.</p> <ul class="social-icons"> <li><a href=""><i class="fab fa-twitter"></i></a></li> <li><a href=""><i class="fab fa-facebook-f"></i></a></li> <li><a href=""><i class="fab fa-linkedin-in"></i></a></li> </ul> </div> </div> </aside> <main> <div class="container"> <div class="about"> <header> <img class="avatar" src="https://source.unsplash.com/7YVZYZeITc8/240x240" alt="Portrait"> <h1>Chisom Uma</h1> </header> <p>I'm a big lover of tech and that's why I write mostly about that for Commonninja. But I'm also passionate about travel and lifestyle.</p> <ul class="social-icons"> <li><a href=""><i class="fab fa-twitter"></i></a></li> <li><a href=""><i class="fab fa-facebook-f"></i></a></li> <li><a href=""><i class="fab fa-linkedin-in"></i></a></li> </ul> </div> </div> </main>

Here, we first define our class=”container, then we deleted the large class from our second element, which allowed the aside and main elements to share the same style. 

Let’s take a look at our CSS:

/* Basic styling */ body { font-family: 'Manrope', sans-serif; color: rgb(45, 46, 48); padding: 2.5rem; display: grid; grid-template-columns: .3fr .7fr; gap: 2.5rem; } h1 { font-family: 'Poppins', sans-serif; } p { color: rgba(45, 46, 48, .75); } /* Avatar */ .avatar { border-radius: 50%; } /* Social icons */ .social-icons { list-style: none; padding: 0; margin: 1.5rem 0 0 0; display: flex; gap: 1rem; } .social-icons li a { background-color: rgba(45, 46, 48, .1); color: rgb(45, 46, 48); display: inline-block; border-radius: 50%; width: 2.5rem; height: 2.5rem; text-align: center; line-height: 2.5rem; } /* Main & Aside */ aside, main { border: 1px dashed; padding: 2.5rem; position: relative; } aside::before, main::before { position: absolute; top: 0; left: 0; padding: .5rem; color: white; font-size: 14px; } aside { border-color: #8950EE; } aside::before { content: 'Aside'; background-color: #8950EE; } main::before { content: 'Main'; background-color: #00B073; } main { border-color: #00B073; } /* Container queries */ .container { container-type: inline-size; } .avatar { width: 4rem; height: 4rem; } .about header { display: flex; align-items: center; gap: 1rem; } h1 { font-size: 21px; } .social-icons { display: none; } @container (min-width: 30rem) { .about { text-align: center; display: flex; flex-direction: column; align-items: center; } .avatar { width: 10rem; height: 10rem; } .about header { display: block; } h1 { font-size: 44px; } .social-icons { display: flex; } }

To get started with container queries, there are two things we did. First, we defined the element that we will query. This will let the browser know which element or container to query and how to query it.

We did this by creating our .container and passing in our container-type: inline-size; which tells us that we want to query the container width or inline dimension. 

Next, we defined a container rule; @container (min-width: 30rem.  Then, inside we added all the changes we wanted to do to the descendants of our container, as shown in the code.

When our container element goes over 30 rem, it applies the styles passed into the @container. Let’s take a look at the demonstration below:

From the demonstration above, you can notice how it changes, and you might say, “but we are still changing the viewport size,” We are changing the viewport size because these elements are directly linked to that. So, changing the viewport size changes the width of the element.

In our .container when we make the following changes to the code:

.container { container-type: inline-size; resize: horizontal; overflow: auto; }

See the result below:

Now, we can resize the main element. Instead of changing the viewport, we are resizing the element. We can see that when the element or container gets smaller, the container query starts implementing, and we now get the initial version of our component. How cool can that be? 

This opens up a new world of possibilities when creating responsive websites because we no longer need to depend on media queries to create multiple versions of an element. For example, a navbar can have three different versions for mobile, tablet, and desktop; we can easily do those versions with container queries.

Demo for Container Queries 

In this section, we are going to look at a more complex example. To follow up with this section, check out the Codepen

This is what our project looks like below:

Our project is made up of a page with many articles displayed in a grid format. And depending on the size of the container for each article, we can have three variations, from the largest article to the medium size to the small size. 

Let us take a look at how Grid, Auto-fit, and Medium work in the case of our project.

Grid and Auto-Fit

Grid allows us to create amazing layouts, thanks to auto-fit. Auto-fit allows the grid to automatically make items bigger or smaller depending on the available space. 

/* Grid */ .grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); grid-gap: 3rem; margin-bottom: 3rem; }

In our .grid we passed in grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));, then we set our grid-gap and margin-bottom to 3rem.

Medium

For our medium style, it’s going to be applied to where the container query dictates that it is necessary. 

/* Medium */ @container articles (min-width: 40rem) { .article-card { display: flex; gap: 2rem; } figure { max-width: 20rem; align-self: flex-start; } h1 + p, .label { display: block; } .meta { order: 0; display: flex; align-items: center; gap: 1rem; } }

Here, we set our min-width to 40rem. In our first row, where the article parent is at least 40 rem, we get an updated version, while the rest of the article gets the default version of our project. 

Adding Color Overlay

To add a color overlay to our first article, we simply did that by creating a pseudo-element, as shown in the code below:

figure::before { content: ''; position: absolute; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(1, 41, 26, .8); }

We first created figure::before, then we set content; to nothing, and our background-color; to rgba(1, 41, 26, .8).

This is what our final project looks like below:

For our first article, you will notice that the parent is very large, passing our minimum of 50rem which we in our /* Large */. So, it uses the large style. The parents of the articles in the second row have that space between them cut in half, as seen in the GIF. So, they are not using the large version; they are using the medium version. Finally, the last four articles use the default version because their parents have limited width.

We have now successfully used container queries to create three different versions of an element based on the parent size and not the viewport size or any kind of media queries. We just used container queries that target the direct size of the container. 

Let’s take a look at this next demonstration:

As we make the project smaller, each article gets smaller and smaller. The middle-row articles now use the default style. Our top article also uses the medium style as we go smaller and smaller. The middle row articles now use the default style, and as we go smaller, our top article will also use the medium style.

Conclusion

This article introduced CSS container queries and how we can use them to create responsive web designs in an application.  
You can read more about browser compatibility for CSS container queries here.