| name | utopia-container-queries |
| description | CSS Container Queries with container units (cqi, cqw, cqh, cqb) combined with Utopia.fyi fluid scales for component-level responsiveness. Use when creating truly modular components that adapt to container size, implementing component-level fluid typography with cqi units, or building context-aware design systems. Extends Utopia philosophy from viewport-based to container-based fluidity. |
| allowed-tools | Read, Write, Edit, Bash |
Utopia Container Queries
Component-level responsiveness using container queries with Utopia.fyi fluid scales
Core Philosophy
Container queries extend Utopia's declarative, fluid design approach from the viewport level to the component level. Instead of components responding only to viewport width, they respond to their container's dimensions, enabling truly modular, context-aware design.
Key Insight: Combine Utopia's fluid scales (viewport-based) with container query units (container-based) for layered responsiveness — global scaling + component adaptation.
When to Use This Skill
- Creating components that adapt to container size, not viewport size
- Implementing fluid typography that scales based on component width
- Building truly reusable components for design systems
- Combining Utopia fluid scales with container-level responsiveness
- Using container query units (cqi, cqw) with clamp() for component typography
- Enabling context-aware layouts (sidebar vs. main content area)
- Progressive enhancement from viewport-based to container-based fluidity
Container Query Fundamentals
Browser Support (2025)
Fully supported: Chrome 105+, Edge 105+, Firefox 110+, Safari 16+ Baseline: ✅ Widely available since February 2023 Browser Compatibility: 82/100
Container Types
/* Long form */
.container {
container-name: card;
container-type: inline-size; /* Queries width (horizontal) or height (vertical) */
}
/* Shorthand */
.container {
container: card / inline-size;
}
Container Types:
inline-size- Query inline dimension (width in LTR, height in vertical writing)size- Query both dimensions (use sparingly, affects layout performance)normal- Default, no containment
Best Practice: Use inline-size for most cases — queries container width without layout side effects.
Basic Container Query Syntax
/* Named container */
@container card (inline-size > 400px) {
.card {
display: grid;
grid-template-columns: 150px 1fr;
}
}
/* Unnamed (uses nearest ancestor container) */
@container (width > 500px) {
.component {
display: flex;
gap: var(--space-m);
}
}
/* Range queries (modern, readable) */
@container (400px <= inline-size <= 800px) {
.element {
/* Styles for containers between 400-800px */
}
}
Container Query Units
Relative sizing units based on container dimensions, not viewport:
Available Units
cqw- 1% of container widthcqh- 1% of container heightcqi- 1% of container inline size (recommended)cqb- 1% of container block sizecqmin- Smaller of cqi or cqbcqmax- Larger of cqi or cqb
Recommended: Use cqi for international audiences — automatically switches to horizontal or vertical axis based on writing mode.
Container Units with clamp()
.card {
/* Responsive padding based on container */
padding: clamp(1rem, 4cqi, 2rem);
/* Fluid typography */
font-size: clamp(1rem, 3cqi, 1.6rem);
/* Dynamic gaps */
gap: clamp(0.5rem, 2cqw, 1.5rem);
}
How This Works:
- At small container widths: padding approaches 1rem (minimum)
- At medium widths: padding scales proportionally (4% of container inline size)
- At large widths: padding caps at 2rem (maximum)
Integrating Container Units with Utopia Scales
Layered Responsiveness Strategy
Layer 1: Viewport-based Utopia fluid scales (global) Layer 2: Container query units (component-level)
:root {
/* Utopia viewport-based type scale */
--step-0: clamp(1rem, 0.9286rem + 0.3571vi, 1.25rem);
--step-1: clamp(1.2rem, 1.1036rem + 0.4821vi, 1.5625rem);
--step-2: clamp(1.44rem, 1.3113rem + 0.6435vi, 1.9531rem);
/* Utopia space scale */
--space-s: clamp(1rem, 0.9286rem + 0.3571vi, 1.25rem);
--space-m: clamp(1.5rem, 1.3929rem + 0.5357vi, 1.875rem);
--space-l: clamp(2rem, 1.8571rem + 0.7143vi, 2.5rem);
}
.card-container {
container: card / inline-size;
}
.card {
/* Base: Utopia fluid scale (viewport-responsive) */
padding: var(--space-m);
font-size: var(--step-0);
/* Additional container-based adjustments */
gap: clamp(var(--space-s), 3cqi, var(--space-m));
}
Why This Works:
- Utopia scales provide global proportional scaling
- Container units add component-level adaptation
- Double fluidity: responds to both viewport AND container
Component Typography with cqi
.card-container {
container: card / inline-size;
}
.card {
/* Base typography from Utopia */
font-size: var(--step-0); /* 16px → 20px (viewport) */
/* Container-based refinement */
@container (inline-size > 400px) {
font-size: clamp(var(--step-0), 2.5cqi + 0.5rem, var(--step-1));
}
}
.card__title {
/* Viewport-based from Utopia */
font-size: var(--step-2);
/* Container-based enhancement */
@container (inline-size > 600px) {
font-size: clamp(var(--step-2), 4cqi, var(--step-3));
}
}
Pattern:
- Start with Utopia step (viewport-based foundation)
- Add container query at breakpoint
- Use cqi with clamp() bounded by Utopia steps
- Result: Smooth scaling within Utopia's systematic constraints
Spacing with Container Units
.card-container {
container: card / inline-size;
}
.card {
/* Base padding from Utopia space scale */
padding: var(--space-s-m); /* 16px → 30px (viewport) */
/* Container-based adjustment */
@container (inline-size > 500px) {
padding: clamp(var(--space-m), 5cqi, var(--space-l));
gap: clamp(var(--space-s), 2cqi, var(--space-m));
}
}
Best Practice: Use Utopia space tokens as min/max bounds for container-based clamp() calculations.
Container Queries vs Media Queries
When to Use Container Queries
✅ Use Container Queries:
- Component-level responsiveness
- Reusable components in varying contexts
- Design system components
- Cards, widgets, modular UI elements
- Layout flexibility within page sections
When to Use Media Queries
✅ Use Media Queries:
- User preferences (
prefers-color-scheme,prefers-reduced-motion) - Page-level layouts
- Print styles
- Device characteristics (hover capability)
- Global theme switches
Best Practice: Use Both Together
/* Global: Media query for dark mode */
@media (prefers-color-scheme: dark) {
:root {
--color-bg: #1a1a1a;
--color-text: #f5f5f5;
}
}
/* Component: Container query for layout */
@container card (inline-size > 400px) {
.card {
display: grid;
grid-template-columns: 150px 1fr;
}
}
Practical Patterns
Adaptive Card Component
.card-container {
container: card / inline-size;
}
.card {
/* Base: Utopia scales */
padding: var(--space-s);
gap: var(--space-xs);
font-size: var(--step-0);
display: grid;
}
/* Small: Stacked layout */
@container card (inline-size < 400px) {
.card {
grid-template-columns: 1fr;
padding: var(--space-s);
}
.card__image {
aspect-ratio: 16 / 9;
}
}
/* Medium: Horizontal layout */
@container card (inline-size >= 400px) {
.card {
grid-template-columns: 150px 1fr;
padding: clamp(var(--space-s), 3cqi, var(--space-m));
gap: clamp(var(--space-xs), 2cqi, var(--space-s));
}
}
/* Large: Enhanced layout */
@container card (inline-size >= 600px) {
.card {
grid-template-columns: 200px 1fr;
padding: clamp(var(--space-m), 4cqi, var(--space-l));
gap: clamp(var(--space-s), 3cqi, var(--space-m));
}
.card__title {
font-size: clamp(var(--step-1), 3cqi, var(--step-2));
}
.card__image {
aspect-ratio: 3 / 4;
}
}
Why This Works:
- Same component code works in sidebar (narrow) or main content (wide)
- Container queries handle layout shifts
- Utopia scales + cqi provide layered fluidity
- Systematic spacing maintained throughout
Responsive Navigation
.nav-container {
container: nav / inline-size;
}
.nav {
display: flex;
gap: var(--space-s);
padding: var(--space-s);
}
/* Narrow: Vertical stack */
@container nav (inline-size < 600px) {
.nav {
flex-direction: column;
gap: var(--space-2xs);
}
.nav__item {
width: 100%;
}
}
/* Wide: Horizontal with fluid gaps */
@container nav (inline-size >= 600px) {
.nav {
flex-direction: row;
gap: clamp(var(--space-s), 2cqi, var(--space-m));
}
}
Article Component
.article-container {
container: article / inline-size;
}
.article {
/* Base typography from Utopia */
font-size: var(--step-0);
line-height: 1.6;
padding: var(--space-m);
}
.article h1 {
font-size: var(--step-3);
margin-block-end: var(--space-m);
}
/* Container-based enhancements */
@container article (inline-size > 600px) {
.article {
padding: clamp(var(--space-m), 4cqi, var(--space-l));
max-width: 70ch;
}
.article h1 {
font-size: clamp(var(--step-3), 5cqi, var(--step-4));
margin-block-end: clamp(var(--space-m), 3cqi, var(--space-l));
}
}
@container article (inline-size > 900px) {
.article {
font-size: clamp(var(--step-0), 2cqi + 0.5rem, var(--step-1));
}
.article h1 {
font-size: clamp(var(--step-4), 6cqi, var(--step-5));
}
}
Grid Item Adaptation
/* Parent uses Utopia grid */
.grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(min(300px, 100%), 1fr));
gap: var(--space-m-l);
}
/* Each grid item is a container */
.grid-item {
container: item / inline-size;
}
/* Item adapts to its grid cell width */
.grid-item__content {
padding: var(--space-s);
font-size: var(--step-0);
}
@container item (inline-size > 350px) {
.grid-item__content {
padding: clamp(var(--space-s), 3cqi, var(--space-m));
display: grid;
grid-template-columns: auto 1fr;
gap: var(--space-s);
}
}
@container item (inline-size > 500px) {
.grid-item__content {
font-size: clamp(var(--step-0), 2.5cqi, var(--step-1));
}
}
Pattern: Grid controls layout, container queries control item internals.
Nested Containers
Multiple containers can coexist with independent queries:
.page-container {
container: page / inline-size;
}
.sidebar-container {
container: sidebar / inline-size;
}
.card-container {
container: card / inline-size;
}
/* Page-level query */
@container page (inline-size > 1200px) {
.page {
display: grid;
grid-template-columns: 300px 1fr;
gap: var(--space-l);
}
}
/* Sidebar-level query */
@container sidebar (inline-size > 250px) {
.sidebar-nav {
font-size: var(--step-0);
gap: var(--space-s);
}
}
/* Card-level query (inside sidebar OR main) */
@container card (inline-size > 400px) {
.card {
display: grid;
grid-template-columns: 150px 1fr;
}
}
Why This Works:
- Each container queries independently
- Card responds to ITS container, not page width
- Same card component works in sidebar (narrow) or main (wide)
- True component modularity
Accessibility Considerations
Always Test with Browser Zoom
/* ⚠️ Can cause accessibility issues */
.component {
font-size: 3cqw; /* No min/max bounds */
}
Problem: When user zooms, container width may not change proportionally, breaking text scaling.
Solution: Always use clamp() with Utopia steps as bounds:
/* ✅ Accessible */
.component {
font-size: clamp(var(--step-0), 2.5cqi, var(--step-1));
}
Testing Checklist:
- Zoom to 200% in browser
- Verify text scales appropriately
- Ensure minimum sizes are readable
- Check that container-based scaling doesn't break zoom
Keep Central rem Value Near 1
/* ✅ Good - users retain zoom control */
font-size: clamp(1rem, 2cqi + 0.5rem, 1.5rem);
/* ⚠️ Problematic - reduces user control */
font-size: clamp(0.5rem, 5cqi, 3rem);
Guideline: Keep the central rem value close to 1rem and cqi value low (2-3cqi) to preserve user zoom control.
Progressive Enhancement
Fallback for Non-Supporting Browsers
/* Base styles (all browsers) */
.card {
padding: var(--space-m);
font-size: var(--step-0);
}
/* Enhanced with container queries */
@supports (container-type: inline-size) {
.card-container {
container: card / inline-size;
}
@container card (inline-size > 400px) {
.card {
display: grid;
grid-template-columns: 150px 1fr;
padding: clamp(var(--space-m), 4cqi, var(--space-l));
}
}
}
Feature Detection in JavaScript
if (CSS.supports('container-type: inline-size')) {
document.documentElement.classList.add('supports-container-queries');
}
/* Default */
.component {
padding: var(--space-m);
}
/* Enhanced when supported */
.supports-container-queries .component-container {
container: component / inline-size;
}
.supports-container-queries .component {
@container (inline-size > 500px) {
padding: clamp(var(--space-m), 4cqi, var(--space-l));
}
}
Best Practices
- Utopia as Foundation - Start with Utopia fluid scales (viewport-based)
- Layer Container Queries - Add container-level responsiveness on top
- Use cqi for Typography - Better internationalization than cqw
- Clamp with Utopia Bounds - Use Utopia steps as min/max in clamp()
- Name Containers - Use descriptive container names for clarity
- Test with Zoom - Always verify 200% browser zoom works
- Avoid Deep Nesting - Limit container query nesting to 2-3 levels
- Single Responsibility - One container type per component
- Progressive Enhancement - Provide fallbacks for older browsers
- Combine with Grid/Flex - Use with
utopia-grid-layoutfor complete system
Common Patterns
Modular Card
.card-container {
container: card / inline-size;
}
.card {
padding: var(--space-s);
gap: var(--space-xs);
font-size: var(--step-0);
}
@container card (inline-size > 400px) {
.card {
display: grid;
grid-template-columns: auto 1fr;
padding: clamp(var(--space-s), 3cqi, var(--space-m));
}
}
Responsive Widget
.widget-container {
container: widget / inline-size;
}
.widget {
padding: var(--space-m);
}
.widget__title {
font-size: var(--step-2);
}
@container widget (inline-size < 300px) {
.widget {
padding: var(--space-s);
}
.widget__title {
font-size: var(--step-1);
}
}
@container widget (inline-size > 600px) {
.widget {
padding: clamp(var(--space-m), 4cqi, var(--space-l));
}
.widget__title {
font-size: clamp(var(--step-2), 4cqi, var(--step-3));
}
}
Context-Aware Component
/* Same component, different contexts */
.component-container {
container: component / inline-size;
}
.component {
/* Base: Works everywhere */
padding: var(--space-s);
font-size: var(--step-0);
}
/* In sidebar (narrow) */
@container component (inline-size < 400px) {
.component {
/* Compact vertical layout */
display: flex;
flex-direction: column;
gap: var(--space-2xs);
}
}
/* In main content (wide) */
@container component (inline-size >= 400px) {
.component {
/* Spacious horizontal layout */
display: grid;
grid-template-columns: auto 1fr;
gap: clamp(var(--space-s), 2cqi, var(--space-m));
}
}
Integration with Other Skills
Prerequisites:
- Use
utopia-fluid-scalesfirst to generate type and space scales - Use
utopia-grid-layoutfor page-level layouts with fluid gaps
Workflow:
- Generate Utopia scales (
utopia-fluid-scales) - Implement page layouts with Grid/Flex (
utopia-grid-layout) - Add container queries for component adaptation (
utopia-container-queries)
Result: Complete Utopia-aligned responsive system with layered fluidity:
- Viewport-based: Global Utopia scales
- Layout-based: Grid/Flex with fluid gaps
- Component-based: Container queries with cqi units
Resources
- MDN Container Queries: https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_containment/Container_queries
- Chrome Guide: https://developer.chrome.com/blog/cq-polyfill
- Container Query Units: https://developer.mozilla.org/en-US/docs/Web/CSS/length#container_query_length_units
- Modern CSS Solutions: https://moderncss.dev/container-query-units-and-fluid-typography/
- Can I Use: https://caniuse.com/css-container-queries
Next Steps
After implementing container queries:
- Test components in multiple contexts (sidebar, main, grid cells)
- Verify browser zoom works correctly (200% test)
- Ensure accessibility with min/max bounds using Utopia steps
- Document container query breakpoints for team reference
- Build component library with container-aware components
- Integrate with design system using Utopia shared vocabulary