Color Mode

Container Queries: Component-Level Responsiveness

For years, web developers have relied on media queries to create responsive designs. Media queries check the viewport size and adjust layouts accordingly. This approach works well for page-level design decisions, but it breaks down when you need individual components to respond to their own available space rather than the entire viewport.

Container queries represent a fundamental shift in how we approach responsive design. Instead of asking "how wide is the browser window?", we can now ask "how much space does this component have?" This changes everything about how we build reusable, responsive components.

The Problem with Media Queries

Consider a card component that needs to adapt its layout. Using media queries, you would write something like this:


        .card {
            display: flex;
            flex-direction: column;
        }

        @media (min-width: 768px) {
            .card {
                flex-direction: row;
            }
        }
    

This works when the card spans the full viewport width. But what happens when you place the same card in a narrow sidebar? The viewport might be 1200px wide, triggering the horizontal layout, but the sidebar is only 300px wide. The card breaks because it is responding to the viewport, not its actual container.

This limitation has plagued component-based design for years. You could not create truly reusable components that adapt to their context. You had to create different versions of components or use complex JavaScript solutions to measure container sizes. Container queries solve this problem elegantly.

Understanding Container Queries

Container queries allow elements to respond to the size of their containing element rather than the viewport. To use container queries, you need two pieces: a container and a query that targets it.

Defining a Container

First, you designate an element as a container using the container-type property:


        .sidebar {
            container-type: inline-size;
        }
    

The inline-size value means the container can be queried based on its inline dimension, which is the width in horizontal writing modes. This is the most common container type. Other values include size (both dimensions) and normal (the default, which means no containment).

When you set container-type: inline-size, you are creating what CSS calls a containment context. The browser tracks the size of this element, allowing descendant elements to query it. This containment is necessary for performance reasons. Without it, the browser would need to track every element, which would be expensive.

Querying the Container

Once you have defined a container, descendant elements can query it using the @container rule:


        .card {
            display: flex;
            flex-direction: column;
        }

        @container (min-width: 500px) {
            .card {
                flex-direction: row;
            }
        }
    

This queries the nearest ancestor container. When that container is at least 500px wide, the card switches to a horizontal layout. The card no longer cares about the viewport size. It only cares about its container.

Named Containers and Scoping

By default, @container queries target the nearest ancestor with container-type set. But what if you have nested containers and need to query a specific one? This is where container names come in.

You can name a container using the container-name property:


        .sidebar {
            container-type: inline-size;
            container-name: sidebar;
        }

        .main-content {
            container-type: inline-size;
            container-name: main;
        }
    

Then query specific containers by name:


        @container sidebar (min-width: 300px) {
            .card {
                padding: var(--spacing-sm);
            }
        }

        @container main (min-width: 800px) {
            .card {
                padding: var(--spacing-lg);
            }
        }
    

There is also a shorthand property that combines both container-type and container-name:


        .sidebar {
            container: sidebar / inline-size;
        }
    

The syntax is container: name / type. This shorthand is convenient when you need to set both properties at once.

Container Query Units

Container queries introduce new length units that are relative to the container size, similar to how viewport units (vw, vh) relate to the viewport. These units make it easy to scale elements proportionally within their container.

These units are particularly useful for fluid typography and spacing that scales with the container:


        .card {
            container-type: inline-size;
        }

        .card h2 {
            font-size: clamp(1.6rem, 5cqi, 3.2rem);
        }

        .card p {
            font-size: clamp(1.4rem, 3cqi, 1.8rem);
        }
    

In this example, the heading size scales between 1.6rem and 3.2rem based on the container's inline size. The clamp() function ensures the size never goes below or above the specified bounds, while the middle value (5cqi) makes it fluid.

Logical Properties and Container Units

The cqi and cqb units use logical dimensions, which respect writing mode. In a horizontal writing mode, cqi refers to width and cqb refers to height. In a vertical writing mode, these swap. This makes your components work correctly in different writing systems without additional code.

Media Queries vs Container Queries: When to Use Each

Container queries do not replace media queries. Each has distinct use cases, and understanding when to use each is crucial for building effective responsive designs.

Use Media Queries For:

Use Container Queries For:

In practice, you will often use both in the same project. Media queries establish the overall page structure, while container queries handle component-level responsiveness. This combination creates designs that adapt both to the viewport and to the specific context where components are placed.

Seeing Container Queries in Action

Ahmad Shadeed has created an excellent interactive guide to CSS container queries that demonstrates the concept with real, working examples. His demos clearly show the difference between media queries and container queries, including the exact scenarios where media queries fail and container queries succeed. The guide includes interactive examples of news sections, dashboard widgets, social feeds, and more, each with working code and the ability to resize containers to see the behavior in action.

Practical Examples

Container queries shine in real-world scenarios where components need to be flexible. Here are some common patterns where container queries make a significant difference.

Responsive Card Grids

A card grid that uses container queries can adapt regardless of where it is placed:


        .card-grid {
            container-type: inline-size;
            display: grid;
            gap: var(--spacing-med);
        }

        @container (min-width: 600px) {
            .card-grid {
                grid-template-columns: repeat(2, 1fr);
            }
        }

        @container (min-width: 900px) {
            .card-grid {
                grid-template-columns: repeat(3, 1fr);
            }
        }
    

This grid works equally well in a full-width main content area or a narrow sidebar. The number of columns adjusts based on available space, not viewport width.

Adaptive Form Layouts

Forms can reorganize themselves based on container space:


        .form-container {
            container-type: inline-size;
        }

        .form-row {
            display: flex;
            flex-direction: column;
            gap: var(--spacing-sm);
        }

        @container (min-width: 500px) {
            .form-row {
                flex-direction: row;
            }

            .form-row > * {
                flex: 1;
            }
        }
    

Form fields stack vertically in narrow spaces and arrange horizontally when space allows, making forms usable in any context.

Navigation Components

A navigation component can switch between layouts based on its container:


        .nav-container {
            container-type: inline-size;
        }

        .nav {
            display: flex;
            flex-direction: column;
            gap: var(--spacing-sm);
        }

        @container (min-width: 600px) {
            .nav {
                flex-direction: row;
                justify-content: space-between;
            }
        }

        @container (min-width: 600px) {
            .nav-links {
                display: flex;
                gap: var(--spacing-med);
            }
        }
    

This navigation works whether it is in a header, sidebar, or footer. It adapts to the space it has rather than assuming where it will be used.

Browser Support and Fallbacks

Container queries have excellent browser support as of 2024. All major modern browsers support container queries, including Chrome, Edge, Safari, and Firefox. However, the specification is still in Working Draft status, meaning the syntax or behavior could theoretically change, though major changes are unlikely at this stage of implementation.

For projects that must support older browsers, you have several fallback strategies:

Progressive Enhancement

Provide a basic layout that works without container queries, then enhance it for supporting browsers:


        /* Base styles work everywhere */
        .card {
            display: flex;
            flex-direction: column;
            gap: var(--spacing-sm);
        }

        /* Enhancement for supporting browsers */
        @supports (container-type: inline-size) {
            .card-container {
                container-type: inline-size;
            }

            @container (min-width: 500px) {
                .card {
                    flex-direction: row;
                }
            }
        }
    

The @supports rule checks for container query support before applying the enhanced styles. Browsers without support use the base mobile-first layout, which remains functional.

Hybrid Approach

Combine media queries and container queries for maximum compatibility:


        /* Fallback with media queries */
        @media (min-width: 768px) {
            .card {
                flex-direction: row;
            }
        }

        /* Override with container queries where supported */
        @supports (container-type: inline-size) {
            .card-container {
                container-type: inline-size;
            }

            /* Reset media query styles */
            .card {
                flex-direction: column;
            }

            @container (min-width: 500px) {
                .card {
                    flex-direction: row;
                }
            }
        }
    

This approach provides media query-based responsiveness for older browsers while taking advantage of container queries in modern browsers. The trade-off is slightly more complex CSS, but it ensures broad compatibility.

Performance Considerations

While container queries are performant, excessive nesting of containers or extremely complex query combinations can impact performance. As with all CSS features, test your implementations and avoid over-engineering. In most cases, container queries perform as well as or better than equivalent JavaScript solutions for component responsiveness.

Container Queries in Practice

To see the ideas from this lesson in action—defining containers, writing @container queries, and building components that respond to their own space—watch the video below. Kevin Powell walks through the same concepts with live examples that reinforce component-level responsiveness.

Key Concepts Summary

Container queries represent a paradigm shift in responsive design. Rather than building components that respond to viewport dimensions, you can now build components that respond to their own available space. This changes how we think about reusability and composition in CSS.

The fundamental concepts to remember are:

As you build components using container queries, you will find that your designs become more flexible and reusable. Components that once required multiple variants or JavaScript solutions can now adapt automatically to their context. This is the power of component-level responsiveness.