Everything you need to know about CSS Container Queries in 2026 — from basic setup to advanced patterns. Practical examples, real-world use cases, and browser support from a senior frontend developer with 20+ years of experience.
If you've ever built a reusable component and struggled to make it look right in every possible parent context — a narrow sidebar, a wide main area, a full-width hero section — CSS Container Queries are the solution you've been waiting for.
Before container queries, the only way to make components responsive was to use media queries, which respond to the viewport size. But a component doesn't care about the viewport — it cares about the space its container gives it. A card component displayed in a 300px sidebar and a 900px content area should adapt to its own available space, not the browser window.
Container queries solve this. They let you query the size of a parent container and apply styles accordingly. This makes components truly self-contained, reusable, and context-aware — a paradigm shift in how we build responsive interfaces.
In this guide, I'll take you from zero to production-ready with container queries. You'll learn the syntax, the units, the pitfalls, and the patterns that work in real applications. I've used container queries on production projects since they landed in Chrome 105 — these are battle-tested techniques, not theoretical examples.
A CSS Container Query is a @container rule that applies
styles based on the dimensions of a named container rather than the viewport.
To create a container, you use the container-type property (or the
shorthand container) on a parent element.
The core idea is simple:
container-type on a parent element@container (min-width: 400px) for child elementsThis is fundamentally different from media queries, which only know about the viewport. A card in a 300px-wide container inside a 1920px viewport triggers container query styles at 300px, not 1920px. The component is truly reusable.
The container-type property defines an element as a query container.
The most common value is inline-size, which creates a container that
tracks the inline (width) dimension:
.card-container { container-type: inline-size; }
You can also name your container using container-name. This is useful
when you have nested containers and need to target a specific one:
.sidebar { container-type: inline-size; container-name: sidebar; } .main-content { container-type: inline-size; container-name: content; }
The shorthand container combines both properties:
/* container: <name> / <type> */ .card-grid { container: card-grid / inline-size; }
Once a container is defined, any descendant can query it with the @container rule:
.card { display: grid; grid-template-columns: 1fr; gap: 1rem; } @container (min-width: 400px) { .card { grid-template-columns: 1fr 1fr; } } @container (min-width: 600px) { .card { grid-template-columns: 1fr 1fr 1fr; } }
When the container is named, reference it in the query:
@container sidebar (min-width: 300px) { .widget { font-size: 1.1rem; } }
💡 Best Practice: Always name your containers when you have nesting. Unnamed queries match
the nearest ancestor container with container-type, which can produce surprising results
in complex layouts. Naming makes the intent explicit.
Alongside container queries came container query units — relative length
units that work just like viewport units (vw, vh) but refer to
the container's dimensions instead of the viewport.
| Unit | Relative To | Equivalent to Viewport Unit | Use Case |
|---|---|---|---|
cqw |
1% of container width | vw |
Horizontal sizing, padding |
cqh |
1% of container height | vh |
Vertical sizing, sticky elements |
cqi |
1% of container inline size | vi |
Preferred for text and spacing in horizontal writing modes |
cqb |
1% of container block size | vb |
Stack spacing, vertical rhythm |
cqmin |
Smaller of cqi and cqb |
vmin |
Responsive icons, square elements |
cqmax |
Larger of cqi and cqb |
vmax |
Full-coverage overlays within a container |
Practical example — fluid typography that scales with the container:
.card-title { font-size: clamp(1rem, 4cqi, 2.5rem); padding: 1cqi 2cqi; } .card-image { width: 100%; height: 30cqh; object-fit: cover; }
With cqi, typography scales proportionally to the container's inline size.
The same component gets smaller text in a narrow sidebar and larger text in a wide
content area — automatically, without a single media query.
💡 Pro tip: Use cqi instead of cqw for
responsive typography. cqi respects the writing mode (it's the inline axis),
so your design works in both horizontal and vertical writing modes without changes.
Container queries don't replace media queries — they complement them. Understanding when to reach for each is the key to a clean responsive architecture.
| Scenario | Use | Why |
|---|---|---|
| Page-level layout (grid columns, sidebar visibility) | @media |
The page structure should respond to viewport size |
| Reusable component styling (cards, widgets, forms) | @container |
Components need to work in any container, regardless of viewport |
| Font size and spacing for readability | cqi units |
Fluid scaling without query overhead; works everywhere |
| Print styles, reduced motion, dark mode | @media |
These are user/browser-level preferences, not layout-dependent |
| Dashboard widget grid that reflows | Both | Media query for the page layout, container queries for individual widgets |
A typical modern responsive project uses media queries for the macro layout (the page grid, header, footer) and container queries for the micro layout (individual components within that grid). This separation keeps your codebase clean and your components genuinely reusable.
Let's build a real component — a product card that adapts to three different container widths without any media queries:
/* Define the container */ .product-grid-cell { container: product-card / inline-size; } /* Base card styles — works in narrowest context */ .product-card { display: flex; flex-direction: column; gap: 0.75rem; padding: 1rem; border-radius: 0.75rem; background: var(--surface); } .product-card__image { width: 100%; aspect-ratio: 1; object-fit: cover; border-radius: 0.5rem; } .product-card__title { font-size: clamp(0.9rem, 3cqi, 1.4rem); font-weight: 600; } .product-card__price { font-size: clamp(1rem, 2.5cqi, 1.5rem); font-weight: 700; color: var(--color-accent); } .product-card__description { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; } .product-card__button { display: none; /* Hidden in narrow layout */ } /* Medium container: side-by-side layout */ @container product-card (min-width: 350px) { .product-card { flex-direction: row; align-items: center; } .product-card__image { width: 120px; height: 120px; flex-shrink: 0; } } /* Wide container: show the CTA button */ @container product-card (min-width: 500px) { .product-card__button { display: inline-flex; } }
This card component works flawlessly in a 250px sidebar (vertical stack, no button), a 400px content column (horizontal layout), and a 600px+ main area (with CTA button). You place it in any grid cell, and it adapts automatically.
Dashboard widgets are the ideal use case for container queries. A widget might need to display data differently depending on whether it's in a 1-column, 2-column, or 3-column grid layout. With container queries, each widget manages its own responsiveness without knowing about the grid:
.dashboard-widget { container: widget / inline-size; padding: 1rem; background: var(--surface); border-radius: 0.75rem; } /* Narrow: stacked chart, minimal labels */ @container widget (max-width: 300px) { .widget-chart { height: 150px; } .widget-legend { display: none; } } /* Medium: show legend, full chart */ @container widget (min-width: 301px and max-width: 500px) { .widget-chart { height: 250px; } .widget-legend { font-size: 0.8rem; } } /* Wide: detailed view with extra metrics */ @container widget (min-width: 501px) { .widget-chart { height: 350px; } .widget-footer { display: flex; } }
Forms need to work in modals, sidebars, and full pages. Container queries make this trivial:
.form-container { container: form-wrapper / inline-size; } @container form-wrapper (min-width: 500px) { .form-row { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; } } @container form-wrapper (min-width: 700px) { .form-row { grid-template-columns: 1fr 1fr 1fr; } }
Navigation components with container queries can switch from hamburger to horizontal menu based on the container width — not the viewport. This is especially useful for nested navigation bars and mega-menus:
.nav-container { container: nav / inline-size; } @container nav (max-width: 400px) { .nav-links-desktop { display: none; } .nav-hamburger { display: block; } } @container nav (min-width: 401px) { .nav-links-desktop { display: flex; } .nav-hamburger { display: none; } }
The most powerful pattern: use a CSS Grid with auto-fill and
minmax for the page layout, then let container queries handle
each cell's content. The grid automatically wraps items into new rows as the
viewport shrinks, and the items adapt to their available width:
.product-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 1.5rem; } .product-grid > * { container: grid-item / inline-size; }
With this pattern, the grid determines how many columns fit, and each grid item uses container queries to style its content. No media queries needed for the component — it responds to the container's width, which is determined by the grid.
Container queries automatically apply containment — specifically,
contain: layout style inline-size when using inline-size.
This means the browser can limit style recalculation to just the container subtree,
which can improve rendering performance compared to global style changes.
However, be mindful of container nesting. If you nest containers inside containers, a size change at the outer level triggers recalculations for all inner containers. For most layouts this is fine — browsers optimize this well — but avoid nesting deeper than 3-4 levels in performance-critical areas.
container-type: size for Height-Aware Components
While inline-size is the recommended default, container-type: size
tracks both width and height. Use this sparingly — it applies full containment
(contain: layout style size), which can cause unexpected layout behavior
because the element's intrinsic size becomes zero before query evaluation.
.aspect-aware-widget { container-type: size; } @container (min-height: 300px) { .widget-content { columns: 2; } }
For most use cases, stick with inline-size. Height-based container queries
are rarely necessary — content usually determines height naturally.
As of May 2026, CSS Container Queries are supported in all major browsers:
Container queries reached Baseline" (widely available) status in early 2024. No polyfills are needed for any browser with reasonable market share. The only gap would be very old browsers (IE11 pre-Chromium Edge), which account for under 0.5% of global traffic in 2026.
The container query units (cqw, cqh, cqi, cqb, cqmin, cqmax) have identical support — they landed alongside the queries and are available in the same browser versions.
Without a name, @container queries the nearest ancestor container.
In deeply nested components, this can target the wrong container. Always name your
containers to make the relationship explicit.
size Instead of inline-size
container-type: size applies full containment, which can break layouts
because the element collapses to zero intrinsic dimensions. Unless you specifically
need height queries, use inline-size.
If you're building a design system or component library, container queries are a natural fit. But if your consumers wrap your component in another container, the query might match the wrong size. Document your container naming conventions clearly.
Not every responsive problem needs a container query. If a component only ever appears in one context (e.g., a site header), a media query is simpler and more predictable. Container queries are valuable when a component appears in multiple contexts with different available widths.
Container queries work beautifully alongside other modern CSS features. Here's how they complement each other:
.product-card { container: product-card / inline-size; & .title { font-size: clamp(1rem, 3cqi, 1.5rem); } @container product-card (min-width: 400px) { & { flex-direction: row; } } }
For more on modern JavaScript features that work alongside these CSS capabilities, see my ES2026 Complete Guide.
Ready to try container queries in your project? Here's the simplest possible setup:
/* 1. Define the container on any parent element */ .responsive-wrapper { container: wrapper / inline-size; } /* 2. Style children that adapt to container width */ .my-component { padding: 1rem; } @container wrapper (min-width: 450px) { .my-component { padding: 2rem; display: grid; grid-template-columns: 1fr 1fr; } }
That's it. Add container to a parent, write @container
for the children, and your component becomes container-aware. The browser handles
the rest — no JavaScript, no ResizeObserver, no framework hacks.
If you're working on a responsive redesign or building a component library, container queries are one of the most impactful CSS features to adopt today. They will fundamentally change how you think about responsive design — for the better.
Need help implementing container queries in your project? I'm available for frontend architecture consulting and development. View my services or contact me directly.
I build production web applications using modern CSS and JavaScript. Let's discuss your project — free consultation.