Sitemap
Kerry Ehikioya

Kerry Ehikioya

Learning and Building Scalable Products

Responsive Elements with CSS Container Queries

8 min readDec 26, 2024

--

Press enter or click to view image in full size
Photo by UX Store on Unsplash

Introduction

Container queries are one of the exciting advancements in CSS, as they offer a new approach to building responsive designs. At the last Devfest Lagos — one of Africa’s largest developer conferences, I attended a talk titled “CSS Rises from the Ashes: (Re)Become Friends with Your Stylesheet” by Olivier.

In his presentation, Olivier highlighted overlooked and new CSS properties, including container queries, and how they can make solving layout challenges easier. Inspired by his talk, I decided to explore container queries more closely and share what I learned about using this new CSS API to build responsive and reusable elements.

Understanding Container Queries

Container queries allow us to design components that respond to the available space in their container. The API provides a way to apply styles to an element based on the size of its surrounding container. They function similarly to media queries but targets HTML elements instead of the viewport.

For the longest time, we’ve relied on media queries to handle responsive design on the web. So, why should you consider using container queries?

Well, the major challenge with media queries is that they were designed to target a device’s media type or viewport size. While useful for creating responsive layouts, they don’t account for querying an element’s dimensions. This makes it difficult to create components that adapt to their immediate surroundings, particularly in scenarios where the layout depends on the size of the container rather than the overall viewport.

To illustrate, let’s explore the classic card component problem.

View on o.5x or full page view

The Card component designed above has a vertical and horizontal layouts. You can try to shrink or expand the viewport in the full page view, to see both views. You would also notice that it responds to change in size of the viewport, switching between layouts depending on the viewport size.

However, when we reuse the same Card component inside an element with a smaller width, such as the Sidebar, the Card doesn’t seem to render the correct layout as expected.

View in 0.5x or full page view

As illustrated above, the Card component tries to render its horizontal layout within the Sidebar. However, because the horizontal layout is too wide for the available space, the UI breaks.
This happens because the Sidebar, despite being smaller in size, isn’t targeted by media queries. Media queries are unable to evaluate the Sidebar’s dimensions to apply the appropriate styles needed to adjust the Card component’s layout.

Even if you try to address this using media queries, you would need additional CSS properties like max-width and a series of CSS tricks to hack through the required design. In most cases, this approach still fails to achieve the desired result.

Problems like these are quite common, especially in single-page applications where a single component is expected to take different compositions or layouts depending on its context.
A great example of this is the Responsive Stepper component, which typically alternates between horizontal and vertical layouts. See the image below:

Designed by Luthfan Kin

A New Era With Container Queries

Container queries were introduced to address this exact challenge, ushering in a new way of approaching fluid designs.

The drivers of this API, including Miriam Suzanne and other contributors, developed proposals that were later adopted by the CSS Working Group (CSSWG). Since its introduction, it has been supported by modern browsers, including Chromium (105+), Safari (16+), and Firefox (110+) and more.

How Do Container Queries Work?

To start using container queries in your application, you need to understand two aspects of the API:

  1. The Containment Context:
    This is where you declare an element as a container. By using the container-type property, you inform the browser that the element's dimensions can be queried.
  2. The @container Rule:
    This is where you query the containment context's size and apply styles to its descendants. It has a structure similar to media queries.

Additionally, the API provides an optional container-name property, which allows you to scope queries to a specific containment context.

Here’s an example of how container queries work:

//...
<div class="post post_1">
<div class="card">
<h2>Card title 500px</h2>
<p>Card content</p>
</div>
</div>

<div class="post post_2">
<div class="card">
<h2>Card title 300px</h2>
<p>Card content</p>
</div>
</div>

//...
.post {
container-type: inline-size; /* Declare a containment context */
container-name: post-container; /* Name the container */

border: 2px dashed black;
margin: 16px 0;
}

.post_1 {
width: 500px;
}

.post_2 {
width: 300px;
}

@container post-container (max-width: 300px) {
.card p {
display: none; /* Hides content when the container width is 300px or less */
}
}

As seen in the code above, we declared a containment context for the .post element and used container queries to apply styles that hide the card content when the width of .post is 300px or less.
When you run the code, this is what it looks like:

Press enter or click to view image in full size
View on Code pen

It’s also worth noting that the styles in a container query are applied to the descendant elements of .post, not to .post itself, since it is the containment context.

Get Kerry Ehikioya’s stories in your inbox

Join Medium for free to get updates from this writer.

Now that we have a solid understanding of what container queries are and how they work in action, let’s resolve the classic card problem mentioned earlier.

Improving the Card Component with Container Queries

To improve the layout of the Card component so that it responds to changes in the surrounding element, we’ll take the following steps:

  1. Convert the Card component into a containment context to apply styles that make it fluid.
  2. Optimize the Card layout for smaller container sizes.
  3. Hide excess content on the Card for the smallest container sizes.

To make testing easier, I’ve made the sidebar adjustable, allowing us to see the impact of container queries in real-time.

Press enter or click to view image in full size
View on Code pen

The structure of the card component from our classic card example looks something like this:

|-- .card_container
|-- .card
|-- .card_img_container
|-- .card_img
|-- .card_content
|-- .card_title
|-- .card_description
|-- .card_author
//... Author details

To convert the card component into a containment context, we will declare .card_container as the containment context, as it wraps the entire card component:

/* New container context */
.card_container {
container-type: inline-size;
container-name: card-container-wrapper; /* Optional container name */
}

Even though we’ve declared the containment context, the card component won’t automatically update its layout. We still need to declare container queries to apply the correct styles for the sizes we’re interested in.

Our classic card example already uses media queries to control the layout, so we can reuse those styles and simply replace the media queries with container query declarations.

Current Media Queries:

/*...*/
@media screen and (width > 300px) {
.card {
.card_author {
display: flex;
align-items: flex-end;
}
.card_img_container {
height: 220px;
}
}
}

@media screen and (width > 400px) {
.card {
.card_img_container {
height: 280px;
}
}
}

@media screen and (width > 700px) {
.card {
flex-direction: row;

.card_img_container {
width: 50%;
}

.card_author {
.card_author_info {
flex-direction: row;

.card_author_details {
order: 2;
}

.card_avatar {
order: 1;
}
}
}
}
}

Converted to Container Queries:

/* New container queries style */
@container card-container-wrapper (width > 300px) {
.card {
.card_author {
display: flex;
align-items: flex-end;
}
.card_img_container {
height: 220px;
}
}
}

/* New container queries style */
@container card-container-wrapper (width > 400px) {
.card {
.card_img_container {
height: 280px;
}
}
}

/* new container queries style */
@container card-container-wrapper (width > 700px) {
.card {
flex-direction: row;

.card_img_container {
width: 50%;
}

.card_author {
.card_author_info {
flex-direction: row;

.card_author_details {
order: 2;
}

.card_avatar {
order: 1;
}
}
}
}
}

When these styles are applied, the card component would render correctly inside elements with varying sizes including the Sidebar as seen below;

When these styles are applied, the card component will render correctly within elements of varying sizes, including the Sidebar, as shown below:

Press enter or click to view image in full size

Lastly, to optimize the card component for even smaller element sizes, we can hide excess content, like the card description and author details:

.card_description {
display: none;
/*...*/
}

.card_author {
display: none;
/*...*/
}

@container card-container (width > 300px) {
.card {
.card_author {
display: flex;
/*...*/
}
}
}

@container card-container (width > 400px) {
.card {
.card_description {
display: block;
}
/*...*/
}
}

As seen below, the card component is now optimized for smaller sizes. You can also add more queries based on your specific needs.

Press enter or click to view image in full size
View on Code pen

Benefits of Using Container Queries in Your Codebase

Now that we’ve seen how container queries can enhance component layouts, let’s explore some benefits they bring to your codebase.

  1. Container queries promote reusability, especially in single-page applications — since you can design one component with different layout variations, you reduce the need to create multiple components for distinct layouts.
  2. They also help encapsulate styles to just the container. With media queries, we typically group styles based on breakpoints, but with named containers, we can leverage CSS scoping to apply styles specifically to the container, reducing style bloat.
  3. Lastly, container queries simplify component composition, making it easier to handle and reason about. Complex compositions, such as layout, theme, and orientation, can be difficult to manage in a single component, even when the content remains relatively similar. However, with the flexibility offered by container queries, we gain more control over these complexities and can clearly define when changes should occur.

Conclusion

The introduction of container queries marks a huge step forward in simplifying responsive design. They make building flexible and scalable layouts smoother and more efficient.

As CSS evolves to meet the demands of the ecosystem, staying updated on features like container queries ensures you’re equipped with the right tools to supercharge your development process.

If you’ve enjoyed this post and want to stay updated on the latest CSS trends, subscribe to my blog for more insights and updates.

Resources

--

Kerry Ehikioya
Kerry Ehikioya

Written by Kerry Ehikioya

Learning and Building Scalable Products