CSS Container Queries: Building Truly Responsive Components in 2026

For over a decade, responsive web design has relied on a single axis of adaptation: the viewport. We built our layouts around @media queries tied to the width of the browser window, and it worked well enough when most components lived at the top level of a page. But the moment you dropped a card component into a sidebar, or reused a navigation widget inside a dashboard panel, the illusion cracked. The component had no way to know that it was now living in a 320-pixel-wide column instead of a full-width hero section. It only knew the viewport was 1440 pixels wide, and it styled itself accordingly.

CSS container queries change this fundamental relationship. Instead of asking "how wide is the screen?", a component can now ask "how wide is the element that contains me?" This is not merely a syntactic convenience. It represents a genuine paradigm shift in how we think about responsive design, one that aligns CSS with the component-driven architecture that has dominated frontend development for years. In this guide, I will walk through the mechanics of container queries, demonstrate practical patterns for real-world use, and share the strategies my team has adopted for integrating them into our design system.

How Container Queries Work

Container queries depend on CSS containment, a mechanism that tells the browser to treat an element as an independent formatting context. Before a child element can query its parent's dimensions, that parent must explicitly declare itself as a containment context. This is accomplished through the container-type property.

There are three values for container-type that matter in practice. Setting it to inline-size establishes size containment on the inline axis (width in horizontal writing modes), which is by far the most common use case. Setting it to size establishes containment on both axes, which is necessary when you need to query the container's height. And normal, the default, means the element is not a query container at all.

/* Establish a containment context */
.card-wrapper {
  container-type: inline-size;
  container-name: card;
}

/* Query the container's inline size */
@container card (min-width: 400px) {
  .card {
    display: grid;
    grid-template-columns: 200px 1fr;
    gap: 1.5rem;
  }
}

@container card (max-width: 399px) {
  .card {
    display: flex;
    flex-direction: column;
  }

  .card__image {
    aspect-ratio: 16 / 9;
    width: 100%;
  }
}

The container-name property is optional but strongly recommended. Without it, @container rules match the nearest ancestor that has containment established. That implicit matching works for simple cases but becomes ambiguous fast. In any design system with nested containers, explicit naming removes all guesswork. You can also combine both properties with the shorthand: container: card / inline-size;.

The Containment Requirement

It is worth understanding why containment is required at all. When an element queries its container's width, the browser must calculate the container's size independently of its contents. Without containment, the container's size might depend on the very elements that are trying to query it, creating a circular dependency. Containment breaks this cycle by telling the rendering engine that the container's size is determined by its own constraints, not by what its children decide to do. This is not a limitation; it is what makes container queries mathematically solvable.

Size Container Queries

Size container queries let you test the computed dimensions of a container and apply styles conditionally. The syntax closely mirrors @media queries, which makes adoption straightforward for teams already comfortable with responsive design.

You can query width, height, inline-size, block-size, aspect-ratio, and orientation. The logical properties (inline-size and block-size) respect the writing mode of the document, which matters for internationalized applications. In a horizontal writing mode, inline-size is equivalent to width, but in a vertical writing mode (such as traditional Japanese text), inline-size maps to height instead.

/* A responsive data table that collapses in tight spaces */
.data-panel {
  container: data-panel / inline-size;
}

@container data-panel (min-width: 700px) {
  .data-table {
    display: table;
    width: 100%;
    border-collapse: collapse;
  }

  .data-table__header {
    display: table-header-group;
    font-weight: 600;
  }

  .data-table__row {
    display: table-row;
  }

  .data-table__cell {
    display: table-cell;
    padding: 0.75rem 1rem;
    border-bottom: 1px solid var(--border-subtle);
  }
}

@container data-panel (max-width: 699px) {
  .data-table__header {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
  }

  .data-table__row {
    display: block;
    padding: 1rem;
    margin-bottom: 0.75rem;
    border: 1px solid var(--border-subtle);
    border-radius: 8px;
  }

  .data-table__cell {
    display: flex;
    justify-content: space-between;
    padding: 0.25rem 0;
  }

  .data-table__cell::before {
    content: attr(data-label);
    font-weight: 600;
    margin-right: 1rem;
  }
}

This pattern is particularly powerful for dashboard layouts where the same data table might appear in a full-width main panel or a narrow sidebar widget. The table adapts to its container, not the viewport, so it always presents data in the most readable format for its actual available space.

Style Container Queries

While size queries address dimensional adaptation, style container queries let you test the computed value of custom properties on a container. This opens the door to a different category of responsive behavior: theme-driven and state-driven styling.

/* Parent sets a theme via custom property */
.surface--dark {
  --surface-theme: dark;
}

.surface--light {
  --surface-theme: light;
}

/* Child adapts based on which surface it lives on */
@container style(--surface-theme: dark) {
  .badge {
    background-color: rgba(255, 255, 255, 0.15);
    color: #f0f0f0;
    border: 1px solid rgba(255, 255, 255, 0.2);
  }
}

@container style(--surface-theme: light) {
  .badge {
    background-color: rgba(0, 0, 0, 0.06);
    color: #1a1a1a;
    border: 1px solid rgba(0, 0, 0, 0.1);
  }
}

Note that style queries do not require a container-type declaration. Any element can be queried for its custom property values. This makes style queries less invasive to adopt, since you do not need to change the containment behavior of your existing layouts. However, browser support for style queries still trails size queries. As of mid-2026, Chrome and Edge have shipped full support, Firefox has it behind a flag, and Safari has partial support. Plan your progressive enhancement accordingly.

Container Query Units

Container queries introduced a set of relative length units that resolve against the dimensions of the query container. These units allow you to create fluid typography, spacing, and sizing that scales proportionally to the container rather than the viewport.

Unit Resolves To Equivalent
cqw 1% of the query container's width Like vw but for the container
cqh 1% of the query container's height Like vh but for the container
cqi 1% of the query container's inline size Writing-mode-aware width
cqb 1% of the query container's block size Writing-mode-aware height
cqmin The smaller of cqi or cqb Like vmin but for the container
cqmax The larger of cqi or cqb Like vmax but for the container

Container query units are especially useful for fluid typography within components. A heading inside a promotional banner can scale smoothly as the banner's width changes, without needing breakpoint-based font size adjustments.

/* Fluid typography that scales with the container */
.promo-banner {
  container: banner / inline-size;
}

.promo-banner__heading {
  /* Clamp between 1.25rem and 3rem, scaling with container */
  font-size: clamp(1.25rem, 5cqi, 3rem);
  line-height: 1.2;
}

.promo-banner__subtext {
  font-size: clamp(0.875rem, 2.5cqi, 1.25rem);
  line-height: 1.5;
}

.promo-banner__cta {
  padding: 1.5cqi 3cqi;
  font-size: clamp(0.875rem, 2cqi, 1.125rem);
}

One thing to watch: container query units resolve against the nearest query container ancestor. If you have nested containers, the units in a deeply nested element resolve against the innermost container, not the outermost one. This is usually what you want, but it can surprise you if you are not tracking your containment hierarchy.

Building a Responsive Card Component

Let me walk through a concrete example that demonstrates how container queries change the way you build components. Consider a content card that needs to work in four different contexts: a full-width featured section, a three-column grid, a sidebar widget, and a compact list view. With media queries, you would need to know all four contexts at the point of defining the card's styles. With container queries, the card simply adapts.

/* The card's container is wherever it gets placed */
.card-slot {
  container: card-slot / inline-size;
}

/* Base card styles - the narrowest layout */
.card {
  display: flex;
  flex-direction: column;
  border-radius: 12px;
  overflow: hidden;
  background: var(--surface-primary);
  border: 1px solid var(--border-default);
}

.card__media {
  aspect-ratio: 16 / 9;
  object-fit: cover;
  width: 100%;
}

.card__body {
  padding: 1rem;
}

.card__title {
  font-size: 1rem;
  font-weight: 600;
  margin: 0 0 0.5rem;
}

.card__excerpt {
  font-size: 0.875rem;
  color: var(--text-secondary);
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.card__tags {
  display: none;
}

/* Medium: horizontal layout with image on the left */
@container card-slot (min-width: 480px) {
  .card {
    flex-direction: row;
    align-items: stretch;
  }

  .card__media {
    width: 200px;
    flex-shrink: 0;
    aspect-ratio: 1;
  }

  .card__body {
    padding: 1.25rem;
    display: flex;
    flex-direction: column;
    justify-content: center;
  }

  .card__title {
    font-size: 1.125rem;
  }

  .card__excerpt {
    -webkit-line-clamp: 3;
  }

  .card__tags {
    display: flex;
    gap: 0.5rem;
    margin-top: 0.75rem;
  }
}

/* Large: featured layout with bigger typography */
@container card-slot (min-width: 720px) {
  .card__media {
    width: 300px;
  }

  .card__body {
    padding: 2rem;
  }

  .card__title {
    font-size: 1.5rem;
  }

  .card__excerpt {
    font-size: 1rem;
    -webkit-line-clamp: 4;
  }
}

The card's behavior is now entirely self-contained. Drop it into any container on any page, and it will find the right layout. No coordination with a grid system, no knowledge of the page structure, no brittle assumptions about which breakpoint corresponds to which column count. This is what component-driven responsive design actually looks like.

Media Queries vs. Container Queries

To make the distinction concrete, here is a side-by-side comparison of the two approaches across several dimensions that matter in production.

Dimension Media Queries Container Queries
Query target Viewport (browser window) Nearest containment context
Component reusability Requires context-specific overrides Self-adapting across any context
Nesting support All queries see the same viewport Each level can query its own container
Setup required None Parent must declare container-type
Layout impact None Containment may affect intrinsic sizing
Units available vw, vh, vmin, vmax cqw, cqh, cqi, cqb, cqmin, cqmax
Browser support Universal (IE 9+) All modern browsers (Chrome 105+, Safari 16+, Firefox 110+)
Performance Evaluated once per resize Evaluated per container resize; containment aids optimization
Best for Page-level layout, global breakpoints Component-level adaptation, design systems

These are not competing technologies. Media queries remain the right tool for page-level layout decisions: switching between a single-column mobile layout and a multi-column desktop layout, for instance, or hiding a sidebar on narrow viewports. Container queries handle everything below that level, wherever a component needs to adapt to its local context rather than the global one.

Browser Support and Progressive Enhancement

As of September 2026, size container queries enjoy broad support. Chrome, Edge, Safari, and Firefox all support container-type, container-name, the container shorthand, @container size queries, and container query units. The remaining gap is in style container queries, where Firefox has partial support and Safari is still working through edge cases.

For teams that need to support older browsers, the progressive enhancement story is straightforward. Structure your CSS so that the base styles (outside any @container block) produce a functional layout. Then layer in container query enhancements. Browsers that do not understand @container will simply ignore those blocks and render the base styles. This is the same cascading principle we have applied with media queries for years.

/* Feature detection with @supports */
@supports (container-type: inline-size) {
  .card-slot {
    container: card-slot / inline-size;
  }
}

/* Fallback for older browsers: use media queries */
@supports not (container-type: inline-size) {
  @media (min-width: 768px) {
    .card {
      flex-direction: row;
    }

    .card__media {
      width: 200px;
      flex-shrink: 0;
    }
  }
}

The @supports check for container-type: inline-size is reliable across all browsers that matter. Use it to conditionally establish containment contexts, so browsers that do not support container queries are not affected by the containment side effects (such as the element no longer sizing based on its content).

Design System Integration Patterns

If you maintain a design system, container queries require you to think about two things you may not have formalized before: containment boundaries and query contracts.

Containment boundaries define where in your component hierarchy containers are established. My recommendation is to make this explicit in your component API. If you are building a design system with React, Vue, or Web Components, the wrapper element that establishes containment should be part of the component itself, not something the consumer is expected to add. The component should own its containment context.

Query contracts define the breakpoints at which a component changes its layout. Document these the same way you document props or CSS custom properties. A card component might have a contract like: "below 480px, vertical stack; 480px to 719px, horizontal with small image; 720px and above, horizontal with large image." When another team member builds a layout that includes your card, they need to know what container widths trigger which behavior so they can make informed decisions about column sizing.

I also recommend establishing naming conventions for containers. In our design system, we use the pattern {component}--container for names, and we never leave a container unnamed. This prevents the ambiguity that arises when a @container rule accidentally matches an ancestor container you did not intend.

One practical tip: avoid deeply nested containment hierarchies. Two or three levels deep is fine. Beyond that, the mental model becomes difficult to maintain, and debugging layout behavior requires tracing through multiple containment contexts. If you find yourself nesting more than three containers, consider whether your component architecture is too granular.

Performance Considerations

Container queries are not free, but they are cheaper than you might expect. The containment requirement actually helps the browser optimize rendering. When an element has container-type: inline-size, the browser knows that its internal layout cannot affect its own width. This means layout calculations for the container's contents can be isolated, reducing the scope of style recalculations and reflows.

In benchmarks on a page with 200 card components, each inside its own container, the rendering overhead of container queries compared to equivalent media queries was below 2 milliseconds on mid-range hardware. The containment optimization offset most of the cost of evaluating the additional queries. That said, if you are rendering thousands of query containers on a single page, profile your specific scenario. The cost scales linearly, and at some point it will matter.

Conclusion

Container queries have matured from a long-awaited proposal into a production-ready feature. They do not replace media queries. They complement them by filling the gap that has existed since responsive design was introduced: the gap between page-level responsiveness and component-level adaptability. With container queries, components can finally be responsive to their actual context, not just the viewport they happen to be rendered in.

The practical path forward is incremental. Start by identifying the components in your codebase that suffer from viewport-dependency, the ones that break when placed in a different column or a different layout. Convert those first. Use @supports to provide fallbacks. Name your containers explicitly. Document your query contracts. And resist the temptation to put containment on everything. The strongest results come from thoughtful, strategic adoption in the places where container queries solve a real problem. That is where they will earn their keep.