Fix: CSS Container Query Not Working — @container and container-type Issues
Part of: React & Frontend Errors
Quick Answer
How to fix CSS container queries not working — setting container-type correctly, understanding containment scope, fixing @container syntax, and handling browser support and specificity issues.
The Problem
A CSS container query has no effect — styles inside @container don’t apply regardless of the element’s size:
/* This has no effect — the card never changes style */
.card-wrapper {
container: sidebar / inline-size;
}
@container sidebar (min-width: 400px) {
.card {
display: flex;
}
}Or the container query fires at the wrong size:
/* Expected to trigger at 400px — but triggers at a different width */
@container (min-width: 400px) {
.item {
font-size: 1.25rem;
}
}Or a specific error in the browser DevTools:
Property 'container-type' is not valid. (old browsers)Or the container query works in isolation but breaks inside a grid or flex layout.
Why This Happens
Container queries are fundamentally different from media queries. Media queries respond to the viewport size, which is a single global value shared by every element on the page. Container queries respond to a specific ancestor element’s size — but only if that ancestor is explicitly established as a containment context. This opt-in requirement is the root cause of most failures.
The CSS containment model requires the browser to know an element’s size independently of its children. Without containment, the element’s size depends on its content, which depends on the container query result, which depends on the element’s size — a circular dependency. Setting container-type: inline-size tells the browser to determine the element’s width without considering its children’s contribution to that width. This breaks the circular dependency and makes container queries possible, but it also means the container must get its width from an external source (parent layout, explicit width, grid column, flex basis). If nothing constrains the container’s width, it collapses.
A second common failure is scope confusion. @container rules style the descendants of the container, never the container itself. This is the opposite of what most developers expect coming from media queries, where @media styles can apply to any element. Trying to change the container’s own padding or background inside @container has no effect because the container is not its own descendant.
- Missing
container-type— the most common cause. Withoutcontainer-type: inline-size(orsize), an element is not a container and@containerrules targeting it never fire. - Wrong ancestor — named containers let you specify which container to query. Without a name, the query uses the nearest ancestor with
container-typeset, which may not be the element you intended. container-type: sizewithout explicit dimensions —sizecontainment requires both inline and block sizes to be defined. If the container’s height isauto, usingcontainer-type: sizemay cause layout collapse.- Browser support — container queries require Chrome 105+, Firefox 110+, and Safari 16+. Older browser versions don’t support them at all.
- Specificity and cascade — styles inside
@containercompete with other CSS rules by specificity. An@containerrule with lower specificity is overridden by a non-container rule.
How @container Compares to Other Responsive Techniques
Container queries solve a specific problem — component-level responsiveness — that media queries and other tools handle poorly or not at all. Understanding the landscape clarifies when @container is the right tool.
@media queries respond to the viewport width. They work well for page-level layout (sidebar collapses below 768px) but fail for reusable components. A card component placed in a 300px sidebar and the same card component placed in a 900px main area both see the same viewport width, so media queries cannot differentiate them. Container queries solve this directly: the card responds to its parent’s width, not the viewport.
@container vs @media — key differences:
| Feature | @media | @container |
|---|---|---|
| Responds to | Viewport size | Ancestor element size |
| Requires setup | No | Yes (container-type) |
| Styles applied to | Any element | Descendants of container only |
| Browser support | All browsers | Chrome 105+, Firefox 110+, Safari 16+ |
| Use case | Page layout | Component layout |
Tailwind CSS v4 added native @container support with the @container variant. You mark a parent with @container (which adds container-type: inline-size) and use responsive variants like @sm:flex or @lg:grid-cols-2 on children. Tailwind’s container variants map directly to @container size queries, so the underlying CSS is identical — Tailwind just provides a utility-class syntax. If your container queries aren’t firing in Tailwind v4, check that the parent has the @container class and the child uses @-prefixed size variants, not the standard sm: / lg: viewport variants.
CSS resize + ResizeObserver (JavaScript approach) — before container queries existed, the standard workaround was a ResizeObserver that watched an element’s size and toggled CSS classes. This approach still works and supports all browsers, but it requires JavaScript, causes layout thrashing if not debounced, and cannot be expressed in pure CSS. Container queries replace this pattern entirely for supported browsers.
Container query polyfills — the container-query-polyfill package (by Google Chrome Labs) enables @container in older browsers by using ResizeObserver and MutationObserver under the hood. It handles basic inline-size queries reliably but does not support style queries, nested containers, or container-type: size. For projects that must support Safari 15 or older Chrome, the polyfill is the bridge, but test edge cases thoroughly.
container-type: inline-size vs size — inline-size establishes containment only in the inline (horizontal) direction. The container can still size itself based on its children’s height. size establishes containment in both directions, which means the container cannot use its children to determine its own height — it collapses to zero unless you set an explicit height or the container is inside a flex/grid layout that constrains its block size. Use inline-size for cards, sidebars, and content areas. Use size only for fixed-dimension containers like modals or media players where you also need to query height.
Fix 1: Set container-type on the Correct Element
The single most common mistake is forgetting container-type on the parent element. Without it, @container rules never apply:
/* WRONG — no container-type, @container rules have no effect */
.card-wrapper {
width: 100%;
}
@container (min-width: 400px) {
.card {
display: flex;
}
}/* CORRECT — set container-type on the parent */
.card-wrapper {
container-type: inline-size; /* This establishes the containment context */
width: 100%;
}
@container (min-width: 400px) {
.card {
display: flex;
}
}Matching HTML structure:
<div class="card-wrapper"> <!-- container-type: inline-size -->
<div class="card"> <!-- @container rules apply to this -->
<h2>Title</h2>
<p>Content</p>
</div>
</div>container-type values and when to use each:
| Value | Containment | Use when |
|---|---|---|
inline-size | Horizontal width only | Querying width (most common) |
size | Both width and height | Querying width or height — requires explicit height |
normal | No size containment (default) | Resets; only name is set |
Common Mistake: Using
container-type: sizewhen you only needinline-size.sizecontainment prevents the element from sizing itself based on its children’s height — which often causes the container to collapse to zero height unexpectedly.
Fix 2: Understand the Scope — @container Styles Apply to Descendants
@container rules style the children of the container, not the container element itself. This is the key mental model shift from media queries:
/* WRONG — trying to style the container based on its own size */
.sidebar {
container-type: inline-size;
}
@container (max-width: 300px) {
.sidebar { /* This doesn't work — .sidebar IS the container */
padding: 0.5rem;
}
}/* CORRECT — style descendants of the container */
.sidebar {
container-type: inline-size;
}
@container (max-width: 300px) {
.sidebar-nav { /* child of .sidebar — this works */
padding: 0.5rem;
}
.sidebar-link { /* child of .sidebar — this works */
font-size: 0.875rem;
}
}If you need the container element itself to change style based on its size, combine a container query on the parent with direct styling, or use a JavaScript ResizeObserver.
Fix 3: Use Named Containers to Target the Right Ancestor
Without a name, @container queries the nearest ancestor with container-type set. Naming containers lets you target a specific ancestor:
/* Unnamed — queries the nearest container ancestor */
.sidebar {
container-type: inline-size;
}
.main-content {
container-type: inline-size;
}
/* This @container targets the nearest container ancestor of .card */
/* If .card is inside .sidebar, it queries .sidebar */
/* If .card is inside .main-content, it queries .main-content */
@container (min-width: 600px) {
.card {
display: grid;
}
}/* Named containers — explicitly target the right ancestor */
.sidebar {
container-type: inline-size;
container-name: sidebar;
/* Shorthand: container: sidebar / inline-size; */
}
.main-content {
container-type: inline-size;
container-name: main;
}
/* This only fires when the sidebar container is at least 200px */
@container sidebar (min-width: 200px) {
.card {
padding: 1rem;
}
}
/* This only fires when the main content container is at least 600px */
@container main (min-width: 600px) {
.card {
display: grid;
grid-template-columns: 1fr 1fr;
}
}Shorthand syntax for both name and type:
/* Long form */
.wrapper {
container-type: inline-size;
container-name: card-container;
}
/* Shorthand: container: <name> / <type> */
.wrapper {
container: card-container / inline-size;
}Fix 4: Fix Layout Collapse with container-type: size
size containment prevents the element from sizing based on its children in both dimensions. If the container has no explicit height, it collapses to zero:
/* WRONG — container-type: size with no explicit height */
.card-grid {
container-type: size; /* height collapses to 0 */
display: grid;
}
/* Grid items can't be seen — container has no height *//* CORRECT option 1 — use inline-size instead of size (most cases) */
.card-grid {
container-type: inline-size; /* Only inline (width) containment */
display: grid;
}
/* CORRECT option 2 — provide explicit height when using size */
.card-grid {
container-type: size;
height: 400px; /* Explicit height prevents collapse */
display: grid;
}
/* CORRECT option 3 — use size for elements with known height */
.modal {
container-type: size;
width: 600px;
height: 80vh; /* Height is known */
}When to use container-type: size vs inline-size:
- Use
inline-sizefor components that reflow based on width (cards, sidebars, articles). - Use
sizefor fixed-size containers where you also need to query height (modals, panels, media players).
Fix 5: Correct @container Syntax
Container query syntax has some differences from media query syntax:
/* Media query syntax */
@media (min-width: 768px) { }
@media screen and (max-width: 1200px) { }
@media (min-width: 400px) and (max-width: 800px) { }
/* Container query syntax — similar but no media type */
@container (min-width: 768px) { }
@container (max-width: 1200px) { }
@container (min-width: 400px) and (max-width: 800px) { }
/* Named container */
@container sidebar (min-width: 200px) { }
@container main (min-width: 600px) and (max-width: 1000px) { }Available container query features (Level 1):
/* Size-based queries */
@container (min-width: 400px) { } /* Width at least 400px */
@container (max-width: 600px) { } /* Width at most 600px */
@container (min-height: 300px) { } /* Height at least 300px (requires container-type: size) */
@container (aspect-ratio: 16/9) { } /* Aspect ratio exactly 16:9 */
@container (min-aspect-ratio: 4/3) { } /* Aspect ratio at least 4:3 */
/* Modern range syntax (Chrome 111+, Firefox 110+) */
@container (width >= 400px) { } /* Equivalent to min-width: 400px */
@container (400px <= width <= 800px) { } /* Range — between 400px and 800px */Style queries (experimental — Chrome 111+):
/* Query a CSS custom property value on the container */
@container style(--variant: card) {
.title {
font-size: 1.5rem;
}
}Fix 6: Handle Browser Support
Container queries are broadly supported in modern browsers, but older versions need a fallback or polyfill:
Browser support (as of 2025):
- Chrome 105+
- Firefox 110+
- Safari 16+
- Edge 105+
- IE: not supported (no plans)
Check support with @supports:
/* Fallback for browsers without container query support */
.card {
/* Default layout for all browsers */
padding: 1rem;
}
@supports (container-type: inline-size) {
.card-wrapper {
container-type: inline-size;
}
@container (min-width: 400px) {
.card {
display: flex;
gap: 1rem;
}
}
}Use the container-query polyfill for older browser support:
npm install container-query-polyfill// Load polyfill for browsers that don't support container queries
const supportsContainerQueries = 'container' in document.documentElement.style;
if (!supportsContainerQueries) {
import('container-query-polyfill');
}Or via CDN with conditional loading:
<script>
if (!('container' in document.documentElement.style)) {
const script = document.createElement('script');
script.src = 'https://cdn.example.com/container-query-polyfill.js';
document.head.appendChild(script);
}
</script>Note: The polyfill has limitations — it works for simple cases but doesn’t support all features like style queries or nested containers. Test thoroughly.
Fix 7: Debugging Container Queries
Use DevTools to inspect container boundaries. In Chrome DevTools, elements with container-type set show a “container” badge in the Elements panel. Click the badge to see the container’s size and which @container rules apply.
Add a visual outline to see the container:
/* Temporary debug styles */
.card-wrapper {
container-type: inline-size;
outline: 2px dashed red; /* See the container boundary */
}Log container size with JavaScript:
const container = document.querySelector('.card-wrapper');
const observer = new ResizeObserver(entries => {
for (const entry of entries) {
console.log('Container width:', entry.contentRect.width);
// Check if this matches when you expect @container rules to fire
}
});
observer.observe(container);Verify the @container threshold with a computed value check:
// Test if the container query is firing correctly
const element = document.querySelector('.card');
const styles = getComputedStyle(element);
console.log('display:', styles.display);
// Should be 'flex' if the @container (min-width: 400px) rule is activeCommon specificity pitfall — container query rules compete with regular CSS by specificity, not by source order alone:
/* This container rule has specificity (0, 1, 0) — one class */
@container (min-width: 400px) {
.card {
display: flex;
}
}
/* This regular rule also has specificity (0, 1, 0) and comes AFTER */
/* It wins because source order breaks the tie */
.card {
display: block;
}
/* Fix — increase specificity of the container rule */
@container (min-width: 400px) {
.card-wrapper .card { /* Now specificity is (0, 2, 0) */
display: flex;
}
}
/* Or reorder so the container rule comes last */
.card {
display: block;
}
@container (min-width: 400px) {
.card {
display: flex; /* Wins because it comes after */
}
}Still Not Working?
Inspect the container in DevTools. Open DevTools, select the container element, and check the Computed styles. Verify that container-type is applied and not overridden. Chrome shows a container icon next to elements with containment.
Check for overflow: hidden on ancestors. If an ancestor element clips the container, the container query may respond to a clipped size rather than the intended layout size.
Verify the container and queried element are in the correct DOM relationship. The element being styled by @container must be a descendant of the container — not a sibling or ancestor.
<!-- CORRECT — .card is a descendant of .wrapper -->
<div class="wrapper"> <!-- container-type: inline-size -->
<div class="card"> <!-- @container applies here -->
Content
</div>
</div>
<!-- WRONG — .card is a sibling of .wrapper -->
<div class="wrapper"></div> <!-- container-type: inline-size -->
<div class="card"></div> <!-- @container does NOT apply here -->In React and CSS Modules, class names may be transformed. Verify the generated class names in DevTools match what you’re querying. In Next.js, CSS Modules generate scoped class names — use the .module.css file’s class references, not raw strings:
/* styles.module.css */
.wrapper {
container-type: inline-size;
}
@container (min-width: 400px) {
.card {
display: flex;
}
}import styles from './styles.module.css';
export function Component() {
return (
<div className={styles.wrapper}>
<div className={styles.card}>Content</div>
</div>
);
}Check for conflicting contain property. If an ancestor has contain: layout or contain: strict, it may interfere with container-type. Container queries require their own containment context, and layering multiple contain declarations can create unexpected containment boundaries. Inspect the ancestor chain in DevTools for any contain values.
Test with a flat DOM structure first. If your container query works in a minimal HTML file but fails in your framework, the issue is likely a generated wrapper element (from a framework component or layout) that sits between the container and the queried element, or an intermediate element with its own container-type that intercepts the unnamed query.
Watch for Tailwind v4 @container class placement. In Tailwind v4, the @container utility class must be on the parent element, and child elements use @sm:, @md:, @lg: variants (not the standard sm:, md:, lg: viewport variants). Mixing viewport and container variants on the same element is valid but confusing — the @-prefixed variants respond to the container, while the non-prefixed variants respond to the viewport.
For related CSS issues, see Fix: CSS Variables Not Working, Fix: CSS Flexbox Not Working, Fix: CSS Grid Not Working, and Fix: Tailwind v4 Not Working.
Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.
Was this article helpful?
Related Articles
Fix: Panda CSS Not Working — Styles Not Applying, Tokens Not Resolving, or Build Errors
How to fix Panda CSS issues — PostCSS setup, panda.config.ts token system, recipe and pattern definitions, conditional styles, responsive design, and integration with Next.js and Vite.
Fix: UnoCSS Not Working — Classes Not Generating, Presets Missing, or Attributify Mode Broken
How to fix UnoCSS issues — Vite plugin setup, preset configuration, attributify mode, icons preset, shortcuts, custom rules, and integration with Next.js, Nuxt, and Astro.
Fix: CodeMirror Not Working — Editor Not Rendering, Extensions Not Loading, or React State Out of Sync
How to fix CodeMirror 6 issues — basic setup, language and theme extensions, React integration, vim mode, collaborative editing, custom keybindings, and read-only mode.
Fix: GSAP Not Working — Animations Not Playing, ScrollTrigger Not Firing, or React Cleanup Issues
How to fix GSAP animation issues — timeline and tween basics, ScrollTrigger setup, React useGSAP hook, cleanup and context, SplitText, stagger animations, and Next.js integration.