Suggest an editImprove this articleRefine the answer for “CSS container queries”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)**CSS container queries** style elements based on their parent container's size, not the viewport. ```css .parent { container-type: inline-size; } @container (min-width: 400px) { .card { display: flex; flex-direction: row; } } ``` **Key point:** `@media` checks browser width. `@container` checks the parent element's width. One component adapts correctly in any layout without context-specific overrides.Shown above the full answer for quick recall.Answer (EN)Image**CSS container queries** let you style elements based on their parent container's size, not the viewport. ## Theory ### TL;DR - Media queries watch the browser window; container queries watch the parent element. - Analogy: media queries are like a central thermostat (the whole house reacts the same); container queries are like a personal thermostat on each device (adapts to its own shelf space). - You need `container-type: inline-size` on the parent before `@container` does anything. - Use container queries for reusable components; use media queries for page-wide layouts. - Browser support: Chrome 105+, Firefox 110+, Safari 16+. ### Quick example Two containers, different widths. Same `.card` inside each. Only the wide one triggers the layout change: ```css .parent { container-type: inline-size; /* declares this as a query container */ } @container (min-width: 400px) { .card { display: flex; flex-direction: row; /* side-by-side only in wide containers */ } } ``` ```html <div class="parent" style="width: 300px"> <div class="card">Narrow: stays stacked</div> </div> <div class="parent" style="width: 500px"> <div class="card">Wide: goes side-by-side</div> </div> ``` The card in the 300px container stays stacked. The card in the 500px container switches to row layout. Browser width changes nothing here. ### Key difference `@media` queries check `window` dimensions via `matchMedia()`. The whole page shares one viewport, so every component using `@media` is coupled to the page context. `@container` works per-element: the browser creates a query list for each declared container and evaluates it independently. The same `.card` can be narrow in a sidebar and wide in a hero section, with no extra CSS. ### When to use - Card component placed in both a sidebar and a main grid: container queries (each adapts locally). - Dashboard widgets of varying widths: container queries (they evaluate independently of each other). - Full-page nav collapse on mobile: media queries (this is a global layout decision). - Print styles or orientation changes: media queries (device-level, not component-level). The simplest rule: if the component gets reused across different layouts, container queries. If you are resizing the page structure itself, media queries. ### Comparison table | Feature | Media Queries | Container Queries | |---|---|---| | Trigger | Viewport (browser) size | Parent container size | | Syntax | `@media (min-width: 768px)` | `@container (min-width: 400px)` | | Scope | Global (whole page) | Local (per container) | | Browser support | IE9+ | Chrome 105+, Firefox 110+, Safari 16+ | | Best for | Page layouts, themes, print | Components in grids and flex layouts | ### How it works internally The browser creates a `ContainerQuery` object for each element with `container-type` declared. Under the hood, this works similarly to `ResizeObserver`: when the container's inline or block size changes, the engine re-evaluates the `@container` rule list and updates styles. If nothing changed, no reflow happens. The container also establishes a containment context, isolating the subtree from the parent layout before the query fires. That is why a container cannot query its own size: the spec prevents circular dependency at the containment boundary. ### Container query units Container queries ship with their own length units: - `cqw` — 1% of the container's width - `cqh` — 1% of the container's height - `cqi` — 1% of the container's inline size - `cqb` — 1% of the container's block size - `cqmin` / `cqmax` — smaller or larger of `cqi` and `cqb` ```css .card-container { container-type: inline-size; } .card { font-size: clamp(1rem, 4cqw, 2rem); /* scales with container, not viewport */ padding: clamp(1rem, 5cqw, 3rem); } ``` For components that live inside other elements, this is more predictable than `vw` units. ### Named containers When you have multiple containers on a page, name them to control which `@container` query applies: ```css .sidebar { container-type: inline-size; container-name: sidebar; } .main-content { container-type: inline-size; container-name: main; } @container sidebar (min-width: 300px) { .card { /* fires only inside .sidebar */ } } @container main (min-width: 600px) { .card { /* fires only inside .main-content */ } } ``` Without a name, `@container` queries the nearest ancestor with `container-type`. ### Common mistakes **Forgetting `container-type` on the parent.** This is the most common issue. Without a declared container, `@container` rules are silently ignored. No error, no warning, just nothing happening. ```css /* wrong: no container declared, @container is ignored */ @container (min-width: 400px) { .card { display: flex; } } /* correct */ .parent { container-type: inline-size; } @container (min-width: 400px) { .card { display: flex; } } ``` **Using `block-size` when `inline-size` is what you need.** `container-type: block-size` works but is rarely required and has spotty support (Firefox added it in 121). For most components, `inline-size` is enough. If you need to query both dimensions, use `container-type: size`. **Nesting containers in flex/grid without isolation.** When a child container resizes, it can trigger the parent container's re-evaluation, which resizes the child again. Chrome 117+ added `self-contained` to break this loop: ```css .inner-container { container-type: inline-size self-contained; } ``` **Over-querying small containers.** A `@container (min-width: 300px)` on a 20px icon element never fires. It wastes evaluation cycles. Check the container's actual minimum width before writing queries against it. **Assuming style queries work without declaring `style` in `container-type`.** Container style queries (Chrome 111+) let you query custom property values, but `style` must be in `container-type`: ```css .card-container { container-type: style inline-size; /* 'style' is required here */ } @container style(--variant: dark) { .card { background: #111; color: #fff; } } ``` ### Real-world usage - **Shadcn/UI** — card components use `@container` so they adapt in any layout without per-context overrides. - **Tailwind CSS v3.2+** — ships `container-type` utilities and an official `@container` plugin. - **Chakra UI** — dashboard tiles in flex grids use container queries to stack or split based on tile width. - **Headless UI** — modal contents resize based on the dialog container width, not the viewport. I started using container queries after spending too much time fighting `@media` breakpoints in a component library where cards appeared in sidebars, grids, and full-width banners at the same time. One set of `@container` rules replaced three sets of layout-specific overrides. ### Follow-up questions **Q:** What is the difference between `container-type: inline-size` and `size`? **A:** `inline-size` tracks only width. `size` tracks both width and height. Use `size` only when the query needs to respond to vertical changes too, since it creates stronger containment constraints and can affect layout more broadly. **Q:** Why can a container not query its own size? **A:** It would create a circular dependency: the element's size depends on its children, which depend on the query result, which depends on the element's size. The spec breaks this by requiring containment to be established before the query evaluates. **Q:** How do container queries interact with CSS containment? **A:** Declaring `container-type` implicitly applies `contain: layout style` (or `contain: layout style size` for the `size` type). This creates an independent subtree and prevents parent layout shift when the container's children change. **Q:** Is there a polyfill? **A:** No full polyfill exists. The feature relies on browser-level `ResizeObserver` internals. The workaround is a JS `ResizeObserver` that adds and removes classes, paired with class-based CSS rules as fallback. **Q:** Performance: 100 container queries vs 100 media queries? **A:** Both use observer-based evaluation internally. In practice, 100+ containers means 100+ observers. Pair with `content-visibility: auto` on off-screen sections to limit active observers at any given time. **Q (senior):** In a grid of 50 identical cards, how would you reduce container query evaluation cost while also supporting style queries? **A:** Declare `container-type: style size self-contained` on a shared ancestor instead of on each card individually. One observer covers the ancestor. Cards read the ancestor's `--theme` custom property via a style query. Per Chromium DevTools traces, this drops from 50 independent observers to one per ancestor group. ## Examples ### Two cards, one component, different containers Same component, same CSS, different output based on parent width. ```html <style> .parent { container-type: inline-size; border: 1px solid #ccc; margin: 20px; padding: 10px; } .narrow { width: 300px; } .wide { width: 560px; } .card { background: #e8f4ff; padding: 16px; display: flex; flex-direction: column; /* default: stacked */ gap: 12px; } @container (min-width: 450px) { .card { flex-direction: row; /* side-by-side when container is wide enough */ align-items: center; } } </style> <div class="parent narrow"> <div class="card"> <img src="avatar.jpg" width="48" height="48" alt="User"> <div> <strong>Anna Smith</strong> <p>Product designer</p> </div> </div> </div> <div class="parent wide"> <div class="card"> <img src="avatar.jpg" width="48" height="48" alt="User"> <div> <strong>Anna Smith</strong> <p>Product designer</p> </div> </div> </div> ``` Narrow parent (300px): image above text. Wide parent (560px): image left, text right. Browser window size is not involved. ### Dashboard widget with named container and `cqi` units A widget that appears in both a sidebar slot (240px) and a full-width panel (900px). No wrapper-specific CSS needed. ```jsx // DashboardCard.jsx function DashboardCard({ title, value, trend }) { return ( <div className="widget"> <div className="chart"> <h3 className="chart__title">{title}</h3> <span className="chart__metric">{value}</span> <span className="chart__trend">{trend}</span> </div> </div> ); } ``` ```css .widget { container-type: inline-size; container-name: dashboard-card; } .chart { padding: 16px; display: grid; grid-template-columns: 1fr; /* single column by default */ gap: 8px; } .chart__metric { font-size: clamp(1.25rem, 6cqi, 2.5rem); /* fluid, tied to container inline size */ } @container dashboard-card (min-width: 450px) { .chart { grid-template-columns: 1fr 2fr; /* chart and detail columns side by side */ } } @container dashboard-card (min-width: 600px) { .chart__metric { font-size: 2.5rem; } } ``` The `cqi` unit scales the metric text relative to the container inline size, not the viewport. Resize the widget slot and the text scales with it. ### Style queries for theme-aware components Style queries (Chrome 111+) let a child read custom properties set on a container. No prop drilling, no extra class names. ```css .card-container { container-type: style inline-size; /* 'style' enables style queries */ container-name: card-wrapper; } .card { background: #fff; color: #111; padding: 20px; border-radius: 8px; } @container card-wrapper style(--variant: dark) { .card { background: #1a1a2e; color: #e0e0e0; } } @container card-wrapper style(--variant: warning) { .card { background: #fff3cd; color: #856404; } } ``` ```html <div class="card-container" style="--variant: dark"> <div class="card">Dark theme card</div> </div> <div class="card-container" style="--variant: warning"> <div class="card">Warning state card</div> </div> <div class="card-container"> <div class="card">Default card (no variant set)</div> </div> ``` The `--variant` custom property on the container acts as a signal. The card reads it without any JS. Skip `style` in `container-type` and the query never fires: all cards fall back to default styles.For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.