For over a decade, responsive web design has relied on a single mechanism: media queries. These viewport-based rules revolutionized how we build websites, but they carry a fundamental limitation — they respond to the browser window, not to the container a component actually lives in. CSS Container Queries change everything. They let components adapt based on the size of their parent container, enabling truly modular, context-aware UI elements that work correctly regardless of where you place them in a layout.
This shift represents the most significant evolution in responsive design since media queries themselves were introduced. If you have been building with responsive design principles, container queries will feel like the missing piece you have always needed. In this guide, we will explore how container queries work, why they matter, and how to use them in production today.
Why Media Queries Fall Short for Component Design
Media queries operate at the viewport level. When you write @media (max-width: 768px), you are asking: “How wide is the browser window?” This approach works well for page-level layouts — switching from a multi-column grid to a single-column stack on smaller screens. But modern web development is component-driven. We build reusable cards, widgets, sidebars, and dashboards that need to live in containers of varying widths.
Consider a product card component. In a three-column grid, each card might occupy 350 pixels of width. In a sidebar, the same card might only get 250 pixels. In a full-width hero section, it could stretch to 800 pixels. With media queries alone, there is no clean way to make a single component adapt to all three contexts. You end up writing brittle CSS that ties the component’s behavior to specific page layouts, defeating the purpose of component reusability.
This problem becomes acute when working with design systems, where components must behave predictably across dozens of different contexts. Container queries solve this by letting each component query its own parent rather than the global viewport.
Understanding Container Queries: Core Concepts
Container queries introduce two new CSS concepts: containment contexts and container query rules. A containment context is an element you designate as a “container” that child elements can query. A container query rule is similar to a media query, but it evaluates against the container’s dimensions instead of the viewport.
Defining a Container
To use container queries, you first establish a containment context on a parent element using the container-type property:
container-type: inline-size— Enables queries based on the container’s inline (horizontal) dimension. This is the most commonly used value.container-type: size— Enables queries based on both inline and block dimensions. Use this when you need to query height as well.container-type: normal— The default. The element is not a query container.
You can also name your containers using container-name, which is essential when you have nested containers and need to query a specific ancestor:
/* Shorthand: container: name / type */
.card-wrapper {
container: card / inline-size;
}
/* Longhand equivalent */
.sidebar {
container-type: inline-size;
container-name: sidebar;
}
Writing Container Query Rules
Once you have a containment context, child elements can query it using @container:
@container (min-width: 400px) {
.card-title {
font-size: 1.5rem;
}
}
/* Query a named container */
@container sidebar (max-width: 300px) {
.nav-item {
padding: 0.5rem;
}
}
The syntax mirrors media queries intentionally, making adoption straightforward for developers already comfortable with responsive design. You can use min-width, max-width, min-height, max-height, and combine conditions with and, or, and not.
Practical Example: Responsive Card Component
Let us build a real-world responsive card component that adapts its layout based on available space. This pattern is invaluable in CSS Grid layouts where cards can span different numbers of columns.
/* === Responsive Card with Container Queries === */
/* 1. Establish the containment context */
.card-container {
container: card / inline-size;
}
/* 2. Base card styles (mobile-first, smallest container) */
.card {
display: grid;
gap: 1rem;
padding: 1.25rem;
border-radius: 12px;
background: #ffffff;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
grid-template-areas:
"image"
"content"
"actions";
}
.card__image {
grid-area: image;
aspect-ratio: 16 / 9;
border-radius: 8px;
overflow: hidden;
}
.card__image img {
width: 100%;
height: 100%;
object-fit: cover;
}
.card__content {
grid-area: content;
}
.card__title {
font-size: 1.125rem;
font-weight: 600;
line-height: 1.3;
margin: 0 0 0.5rem 0;
}
.card__description {
font-size: 0.875rem;
color: #57534e;
line-height: 1.6;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
.card__meta {
display: none; /* Hidden in small containers */
font-size: 0.75rem;
color: #a8a29e;
margin-top: 0.75rem;
}
.card__actions {
grid-area: actions;
display: flex;
gap: 0.5rem;
}
.card__actions button {
flex: 1;
padding: 0.5rem 1rem;
border: none;
border-radius: 6px;
font-size: 0.8125rem;
font-weight: 500;
cursor: pointer;
}
/* 3. Medium container: side-by-side layout */
@container card (min-width: 420px) {
.card {
grid-template-columns: 180px 1fr;
grid-template-areas:
"image content"
"image actions";
align-items: start;
}
.card__image {
aspect-ratio: 1;
height: 100%;
}
.card__description {
-webkit-line-clamp: 3;
}
.card__meta {
display: block;
}
.card__actions {
align-self: end;
}
.card__actions button {
flex: none;
}
}
/* 4. Large container: featured card layout */
@container card (min-width: 680px) {
.card {
grid-template-columns: 280px 1fr;
grid-template-rows: 1fr auto;
padding: 1.5rem;
gap: 1.5rem;
}
.card__title {
font-size: 1.5rem;
margin-bottom: 0.75rem;
}
.card__description {
font-size: 1rem;
-webkit-line-clamp: 4;
}
.card__meta {
display: flex;
gap: 1rem;
margin-top: 1rem;
font-size: 0.8125rem;
}
.card__actions {
justify-content: flex-start;
gap: 0.75rem;
}
.card__actions button {
padding: 0.625rem 1.5rem;
font-size: 0.875rem;
}
}
/* 5. Extra-large container: hero-style card */
@container card (min-width: 900px) {
.card {
grid-template-columns: 400px 1fr;
padding: 2rem;
}
.card__title {
font-size: 1.875rem;
}
.card__description {
-webkit-line-clamp: unset;
font-size: 1.0625rem;
}
}
The beauty of this approach is that you drop the .card-container anywhere — a three-column grid, a sidebar, a modal, a full-width section — and the card automatically adapts. No media queries, no layout-specific overrides, no JavaScript resize observers. The component is truly self-contained.
Practical Example: Adaptive Dashboard Layout
Dashboard interfaces are another area where container queries shine. Dashboard panels often appear in resizable columns, draggable tiles, or collapsible sidebars. Let us build a statistics panel that adapts its presentation based on available space.
/* === Adaptive Dashboard Panel with Container Queries === */
/* Container setup for each dashboard panel slot */
.dashboard-slot {
container: panel / inline-size;
}
/* Base panel: compact, single-value display */
.stats-panel {
background: #fafaf9;
border: 1px solid #e7e5e4;
border-radius: 12px;
padding: 1rem;
}
.stats-panel__header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 0.75rem;
}
.stats-panel__title {
font-size: 0.8125rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.05em;
color: #78716c;
}
.stats-panel__icon {
width: 32px;
height: 32px;
border-radius: 8px;
display: grid;
place-items: center;
background: #c2724e;
color: white;
}
.stats-panel__value {
font-size: 2rem;
font-weight: 700;
color: #1c1917;
line-height: 1;
}
.stats-panel__trend {
display: inline-flex;
align-items: center;
gap: 0.25rem;
margin-top: 0.5rem;
font-size: 0.8125rem;
font-weight: 500;
}
.stats-panel__trend--up { color: #16a34a; }
.stats-panel__trend--down { color: #dc2626; }
.stats-panel__chart {
display: none; /* Hidden in compact mode */
}
.stats-panel__breakdown {
display: none; /* Hidden in compact mode */
}
.stats-panel__actions {
display: none; /* Hidden in compact mode */
}
/* Medium panel: show sparkline chart */
@container panel (min-width: 320px) {
.stats-panel {
padding: 1.25rem;
}
.stats-panel__chart {
display: block;
margin-top: 1rem;
height: 60px;
background: linear-gradient(to right, #c2724e22, #c2724e11);
border-radius: 6px;
position: relative;
overflow: hidden;
}
.stats-panel__value {
font-size: 2.25rem;
}
}
/* Wide panel: side-by-side value and chart */
@container panel (min-width: 480px) {
.stats-panel {
display: grid;
grid-template-columns: 1fr 1fr;
grid-template-rows: auto 1fr auto;
gap: 0 1.5rem;
padding: 1.5rem;
grid-template-areas:
"header header"
"metrics chart"
"actions actions";
}
.stats-panel__header {
grid-area: header;
}
.stats-panel__metrics {
grid-area: metrics;
display: flex;
flex-direction: column;
justify-content: center;
}
.stats-panel__chart {
grid-area: chart;
height: 120px;
margin-top: 0;
}
.stats-panel__actions {
grid-area: actions;
display: flex;
gap: 0.5rem;
margin-top: 1rem;
padding-top: 1rem;
border-top: 1px solid #e7e5e4;
}
}
/* Large panel: show detailed breakdown */
@container panel (min-width: 640px) {
.stats-panel {
grid-template-columns: 200px 1fr 200px;
grid-template-areas:
"header header header"
"metrics chart breakdown"
"actions actions actions";
}
.stats-panel__breakdown {
grid-area: breakdown;
display: flex;
flex-direction: column;
gap: 0.5rem;
justify-content: center;
padding-left: 1.5rem;
border-left: 1px solid #e7e5e4;
}
.stats-panel__breakdown-item {
display: flex;
justify-content: space-between;
font-size: 0.8125rem;
}
.stats-panel__breakdown-label {
color: #78716c;
}
.stats-panel__breakdown-value {
font-weight: 600;
color: #1c1917;
}
.stats-panel__chart {
height: 140px;
}
.stats-panel__value {
font-size: 2.5rem;
}
}
/* Extra-wide panel: full dashboard widget */
@container panel (min-width: 860px) {
.stats-panel {
grid-template-columns: 240px 1fr 240px;
padding: 2rem;
}
.stats-panel__chart {
height: 180px;
}
.stats-panel__actions {
justify-content: flex-end;
}
}
/* === Dashboard Grid (uses traditional media queries for page layout) === */
.dashboard-grid {
display: grid;
gap: 1rem;
grid-template-columns: 1fr;
}
@media (min-width: 640px) {
.dashboard-grid {
grid-template-columns: repeat(2, 1fr);
}
}
@media (min-width: 1024px) {
.dashboard-grid {
grid-template-columns: repeat(3, 1fr);
}
}
@media (min-width: 1400px) {
.dashboard-grid {
grid-template-columns: repeat(4, 1fr);
}
}
Notice the separation of concerns: media queries handle the page-level grid (how many columns the dashboard has), while container queries handle how each panel renders within its allocated space. This is the ideal pattern — media queries for layout, container queries for components. Teams building dashboards with tools like Taskee can leverage this pattern to create panels that reorganize themselves as users resize columns or collapse sidebars.
Container Query Units: Sizing Relative to the Container
Container queries also introduce new CSS units that resolve relative to the query container’s dimensions:
cqw— 1% of the container’s widthcqh— 1% of the container’s heightcqi— 1% of the container’s inline sizecqb— 1% of the container’s block sizecqmin— the smaller ofcqiorcqbcqmax— the larger ofcqiorcqb
These units are incredibly useful for fluid typography and spacing that scales with the component’s container rather than the viewport:
.card-container {
container-type: inline-size;
}
.card__title {
/* Font size scales between roughly 1rem and 2rem
depending on container width */
font-size: clamp(1rem, 4cqi, 2rem);
}
.card__padding {
/* Padding scales with container */
padding: clamp(0.75rem, 3cqi, 2rem);
}
Compare this to viewport units (vw, vh), which always reference the browser window. Container query units give you the same fluid scaling behavior but scoped to the component’s actual available space.
Container Style Queries
Beyond dimensional queries, the CSS specification also includes style queries — the ability to query the computed style of a container. While browser support is still evolving (Chrome supports querying custom properties), the concept is powerful:
.theme-wrapper {
--theme: dark;
}
@container style(--theme: dark) {
.card {
background: #292524;
color: #fafaf9;
}
}
@container style(--theme: light) {
.card {
background: #ffffff;
color: #1c1917;
}
}
Style queries open up pattern possibilities for theming, feature flags, and configuration-driven styling. When combined with CSS custom properties, they provide a pure CSS mechanism for dark mode implementation and other theme variations without JavaScript class toggling.
Browser Support and Progressive Enhancement
Container queries enjoy strong browser support as of 2025. All major browsers — Chrome (105+), Firefox (110+), Safari (16+), and Edge (105+) — support @container size queries. According to usage data, over 92% of global users now have a browser that supports container queries.
For the remaining users, you can use the @supports rule for progressive enhancement:
/* Base styles using media queries as fallback */
@media (min-width: 768px) {
.card {
display: grid;
grid-template-columns: 200px 1fr;
}
}
/* Enhanced styles for browsers with container query support */
@supports (container-type: inline-size) {
.card-wrapper {
container-type: inline-size;
}
/* Reset media query layout */
.card {
grid-template-columns: 1fr;
}
@container (min-width: 400px) {
.card {
grid-template-columns: 200px 1fr;
}
}
}
This layered approach ensures that older browsers still get a reasonable layout via media queries, while modern browsers receive the superior container-query-driven behavior.
Container Queries vs. Media Queries: When to Use Each
Container queries do not replace media queries — they complement them. Understanding when to use each is essential for clean, maintainable CSS architecture:
Use media queries for:
- Page-level layout decisions (number of grid columns, sidebar visibility)
- Global typography scaling
- Print stylesheets
- Orientation-specific adjustments
- Preference queries (
prefers-reduced-motion,prefers-color-scheme)
Use container queries for:
- Component-level layout adaptation
- Design system components that appear in varying contexts
- Dashboard widgets and panels
- Card layouts within grids
- Navigation components in different containers (header vs. sidebar)
- Any element that needs to respond to its available space
The ideal architecture uses media queries at the page shell level and container queries at the component level. This mirrors how teams structure projects with CSS Grid for layout and Flexbox for component internals.
Performance Considerations
Containment is the mechanism that makes container queries possible, and it also has performance implications — mostly positive ones. When you set container-type: inline-size, you are telling the browser that the element’s inline size does not depend on its children. This establishes a layout boundary that can actually reduce unnecessary recalculations.
However, there are a few things to keep in mind for optimal performance:
- Avoid
container-type: sizeunless needed. Full size containment is more restrictive and can cause unexpected behavior with intrinsic sizing. Useinline-sizein most cases. - Do not over-nest containers. While nesting is supported, deeply nested containment contexts can complicate the rendering pipeline. Keep your container hierarchy shallow when possible.
- Be mindful of container query units in animations. Animating properties that use
cqiorcqwunits can trigger layout recalculations on every frame if the container is also being resized.
In practice, container queries perform well. The containment model was specifically designed for performance, and most applications will not encounter any measurable overhead.
Working with Component Frameworks
Container queries integrate smoothly with modern component frameworks. Whether you are building with React, Vue, Svelte, or Web Components, the CSS-native approach means no additional JavaScript is needed for responsive behavior.
In a React component, for instance, you simply apply the container styles to the wrapper element:
// React component — CSS handles all responsive logic
function ProductCard({ product }) {
return (
<div className="card-container">
<article className="card">
<div className="card__image">
<img src={product.image} alt={product.name} />
</div>
<div className="card__content">
<h3 className="card__title">{product.name}</h3>
<p className="card__description">{product.description}</p>
</div>
</article>
</div>
);
}
No useEffect, no ResizeObserver, no state management for breakpoints. The component works correctly in any container width purely through CSS. This dramatically simplifies codebases and eliminates a common source of bugs and performance issues.
CSS framework ecosystems are adapting too. If you are evaluating frameworks, comparing options like those in our Tailwind CSS vs. Bootstrap analysis will help you choose one that supports container queries natively.
Common Patterns and Best Practices
After working with container queries in production projects, several patterns have emerged as best practices:
1. Name Your Containers
Always use container-name in production code. Anonymous containers work in simple cases, but named containers prevent ambiguity when components are nested:
.sidebar { container: sidebar / inline-size; }
.main-content { container: main / inline-size; }
/* Explicitly query the sidebar container */
@container sidebar (max-width: 280px) {
.nav-link span { display: none; }
}
2. Design Mobile-First Within Containers
Just as with media queries, write your base styles for the smallest expected container size, then use min-width container queries to progressively enhance:
/* Base: single column, compact */
.widget { display: block; }
/* When there is enough space: two columns */
@container (min-width: 500px) {
.widget { display: grid; grid-template-columns: 1fr 1fr; }
}
3. Document Container Breakpoints
Create a reference map of your component breakpoints. Unlike media queries (which often share standard breakpoints like 768px, 1024px), container query breakpoints are component-specific. A shared reference prevents inconsistencies across teams. Professional project management tools like Toimi can help teams maintain design documentation and track component specifications across projects.
4. Combine with Modern CSS Features
Container queries work beautifully alongside other modern CSS features like cascade layers, nesting, and :has(). Use them together for expressive, maintainable stylesheets:
@layer components {
.card-container {
container: card / inline-size;
}
.card {
/* Base styles */
&__title {
font-size: clamp(1rem, 3cqi, 1.75rem);
}
/* Container-aware adjustments */
@container card (min-width: 500px) {
&__title { line-height: 1.2; }
}
}
}
Real-World Migration Strategy
Migrating an existing codebase to container queries does not require a complete rewrite. A practical approach involves three steps:
- Identify component-level media queries. Search your CSS for media queries that control component appearance rather than page layout. These are candidates for conversion.
- Add containment contexts. Wrap target components with a container element (or use the existing wrapper) and apply
container-type: inline-size. - Convert incrementally. Replace media queries with container queries one component at a time. Use
@supportsto maintain the media query fallback during the transition.
This component-by-component migration lets you adopt container queries at your own pace without disrupting the existing layout system.
The Road Ahead
The CSS Working Group continues to refine the container queries specification. Upcoming developments include broader support for style queries beyond custom properties, container query support for scroll-state (querying whether a container is scrolled), and potential integration with the View Transitions API for container-aware animations.
Container queries have already transformed how front-end developers think about responsive design. The shift from “How wide is the viewport?” to “How wide is my container?” aligns CSS with the component-driven architecture that dominates modern web development. As browser support reaches near-universal coverage and tooling matures, container queries will become as fundamental to CSS as media queries are today.
FAQ
What is the difference between CSS container queries and media queries?
Media queries respond to the viewport (browser window) dimensions, while container queries respond to the dimensions of a specific parent element. This means a component using container queries adapts based on the space it actually occupies, not the overall screen size. Media queries remain ideal for page-level layout decisions, while container queries excel at making individual components responsive to their context.
Are CSS container queries supported in all major browsers?
Yes. As of 2025, container size queries are supported in Chrome 105+, Firefox 110+, Safari 16+, and Edge 105+, covering over 92% of global browser usage. Container style queries have more limited support, with Chrome leading implementation. For older browsers, you can use progressive enhancement with @supports (container-type: inline-size) to provide media-query-based fallbacks.
Do container queries affect page performance?
Container queries generally have a neutral to positive effect on performance. The containment model they rely on creates layout boundaries that can reduce unnecessary recalculations. For best results, use container-type: inline-size instead of size unless you need height queries, avoid deeply nesting containment contexts, and be cautious with container query units in animations that involve resizing containers.
Can I use container queries with React, Vue, or other JavaScript frameworks?
Absolutely. Container queries are a pure CSS feature and work with any JavaScript framework without additional libraries or hooks. You apply container-type to a wrapper element in your component, and the browser handles all responsive behavior. This eliminates the need for JavaScript-based solutions like ResizeObserver or window resize event listeners for component-level responsiveness.
How do I migrate an existing project from media queries to container queries?
Migrate incrementally rather than rewriting everything at once. Start by identifying media queries that control component-level styling rather than page layout. Add container-type: inline-size to the parent elements of those components. Then convert the media queries to container queries one component at a time, using @supports to keep the original media queries as fallbacks for older browsers. This approach lets you adopt container queries gradually without risk.