Back to Blog
CSSResponsiveContainer QueriesLayout

CSS Container Queries Guide

Master CSS container queries for component-based responsive design. From basics to advanced patterns.

B
Bootspring Team
Engineering
July 8, 2020
6 min read

Container queries enable responsive components based on container size, not viewport. Here's how to use them.

Basic Container Queries#

1/* Define a containment context */ 2.card-container { 3 container-type: inline-size; 4} 5 6/* Or with a name */ 7.card-container { 8 container-type: inline-size; 9 container-name: card; 10} 11 12/* Shorthand */ 13.card-container { 14 container: card / inline-size; 15} 16 17/* Query the container */ 18@container (min-width: 400px) { 19 .card { 20 display: flex; 21 gap: 1rem; 22 } 23} 24 25/* Query named container */ 26@container card (min-width: 400px) { 27 .card-content { 28 flex: 1; 29 } 30}

Container Types#

1/* inline-size: Query width only */ 2.container { 3 container-type: inline-size; 4} 5 6/* size: Query both width and height */ 7.container { 8 container-type: size; 9} 10 11/* normal: No containment (default) */ 12.container { 13 container-type: normal; 14} 15 16/* Combined with other containment */ 17.container { 18 container-type: inline-size; 19 contain: layout style; /* Added automatically */ 20}

Responsive Card Component#

1/* Container setup */ 2.card-wrapper { 3 container-type: inline-size; 4} 5 6/* Base card styles (mobile-first) */ 7.card { 8 display: grid; 9 gap: 1rem; 10 padding: 1rem; 11 background: white; 12 border-radius: 8px; 13 box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); 14} 15 16.card-image { 17 aspect-ratio: 16 / 9; 18 border-radius: 4px; 19 overflow: hidden; 20} 21 22.card-image img { 23 width: 100%; 24 height: 100%; 25 object-fit: cover; 26} 27 28/* Medium container */ 29@container (min-width: 300px) { 30 .card { 31 grid-template-columns: 120px 1fr; 32 align-items: start; 33 } 34 35 .card-image { 36 aspect-ratio: 1; 37 } 38} 39 40/* Large container */ 41@container (min-width: 500px) { 42 .card { 43 grid-template-columns: 200px 1fr; 44 padding: 1.5rem; 45 gap: 1.5rem; 46 } 47 48 .card-title { 49 font-size: 1.5rem; 50 } 51}

Grid Layout with Container Queries#

1/* Responsive grid items */ 2.grid { 3 display: grid; 4 grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); 5 gap: 1rem; 6} 7 8.grid-item { 9 container-type: inline-size; 10} 11 12.item-content { 13 padding: 1rem; 14} 15 16/* Adapt based on item width */ 17@container (min-width: 300px) { 18 .item-content { 19 display: flex; 20 gap: 1rem; 21 } 22 23 .item-text { 24 flex: 1; 25 } 26} 27 28@container (min-width: 400px) { 29 .item-content { 30 flex-direction: row-reverse; 31 } 32 33 .item-image { 34 width: 50%; 35 } 36}

Sidebar and Main Content#

1/* Layout */ 2.layout { 3 display: grid; 4 grid-template-columns: 1fr; 5} 6 7@media (min-width: 768px) { 8 .layout { 9 grid-template-columns: 250px 1fr; 10 } 11} 12 13/* Sidebar container */ 14.sidebar { 15 container-type: inline-size; 16} 17 18.sidebar-nav { 19 display: flex; 20 flex-direction: column; 21 gap: 0.5rem; 22} 23 24/* Collapsed sidebar */ 25@container (max-width: 200px) { 26 .sidebar-nav { 27 align-items: center; 28 } 29 30 .nav-label { 31 display: none; 32 } 33 34 .nav-icon { 35 font-size: 1.5rem; 36 } 37} 38 39/* Main content container */ 40.main-content { 41 container-type: inline-size; 42} 43 44/* Adapt content based on available space */ 45@container (min-width: 600px) { 46 .content-grid { 47 grid-template-columns: repeat(2, 1fr); 48 } 49} 50 51@container (min-width: 900px) { 52 .content-grid { 53 grid-template-columns: repeat(3, 1fr); 54 } 55}

Component Library Patterns#

1/* Button sizes based on container */ 2.button-container { 3 container-type: inline-size; 4} 5 6.btn { 7 padding: 0.5rem 1rem; 8 font-size: 0.875rem; 9} 10 11@container (min-width: 200px) { 12 .btn { 13 padding: 0.75rem 1.5rem; 14 font-size: 1rem; 15 } 16} 17 18@container (min-width: 300px) { 19 .btn { 20 padding: 1rem 2rem; 21 font-size: 1.125rem; 22 } 23} 24 25/* Form layout adaptation */ 26.form-container { 27 container-type: inline-size; 28} 29 30.form-group { 31 display: grid; 32 gap: 0.5rem; 33} 34 35@container (min-width: 400px) { 36 .form-group { 37 grid-template-columns: 150px 1fr; 38 align-items: center; 39 } 40 41 .form-label { 42 text-align: right; 43 } 44}

Dashboard Widgets#

1/* Widget container */ 2.widget { 3 container-type: inline-size; 4 padding: 1rem; 5 background: white; 6 border-radius: 8px; 7} 8 9/* Stats widget */ 10.stats-widget { 11 display: grid; 12 gap: 1rem; 13} 14 15.stat-item { 16 text-align: center; 17} 18 19@container (min-width: 300px) { 20 .stats-widget { 21 grid-template-columns: repeat(2, 1fr); 22 } 23} 24 25@container (min-width: 500px) { 26 .stats-widget { 27 grid-template-columns: repeat(4, 1fr); 28 } 29} 30 31/* Chart widget */ 32.chart-widget { 33 display: grid; 34 gap: 1rem; 35} 36 37@container (min-width: 400px) { 38 .chart-widget { 39 grid-template-columns: 1fr 200px; 40 } 41 42 .chart-legend { 43 order: 1; 44 } 45}

Table Responsiveness#

1.table-container { 2 container-type: inline-size; 3 overflow-x: auto; 4} 5 6.data-table { 7 width: 100%; 8} 9 10/* Stack on small containers */ 11@container (max-width: 500px) { 12 .data-table thead { 13 display: none; 14 } 15 16 .data-table tr { 17 display: block; 18 margin-bottom: 1rem; 19 border: 1px solid #eee; 20 border-radius: 8px; 21 padding: 1rem; 22 } 23 24 .data-table td { 25 display: flex; 26 justify-content: space-between; 27 padding: 0.5rem 0; 28 border: none; 29 } 30 31 .data-table td::before { 32 content: attr(data-label); 33 font-weight: bold; 34 } 35} 36 37/* Full table on larger containers */ 38@container (min-width: 500px) { 39 .data-table th, 40 .data-table td { 41 padding: 0.75rem; 42 text-align: left; 43 border-bottom: 1px solid #eee; 44 } 45}

Container Query Units#

1/* Container query length units */ 2.element { 3 /* Relative to container's inline size */ 4 width: 50cqi; 5 6 /* Relative to container's block size */ 7 height: 25cqb; 8 9 /* Relative to smaller dimension */ 10 font-size: 5cqmin; 11 12 /* Relative to larger dimension */ 13 padding: 2cqmax; 14 15 /* Relative to inline and block */ 16 margin: 2cqi 1cqb; 17} 18 19/* Fluid typography based on container */ 20.container { 21 container-type: inline-size; 22} 23 24.title { 25 font-size: clamp(1rem, 5cqi, 3rem); 26} 27 28/* Responsive spacing */ 29.section { 30 padding: clamp(1rem, 4cqi, 3rem); 31}

Nested Containers#

1/* Outer container */ 2.outer { 3 container: outer / inline-size; 4} 5 6/* Inner container */ 7.inner { 8 container: inner / inline-size; 9} 10 11/* Query outer container */ 12@container outer (min-width: 800px) { 13 .outer-content { 14 display: flex; 15 } 16} 17 18/* Query inner container */ 19@container inner (min-width: 300px) { 20 .inner-content { 21 grid-template-columns: 1fr 1fr; 22 } 23} 24 25/* Query nearest container (default) */ 26@container (min-width: 400px) { 27 .content { 28 flex-direction: row; 29 } 30}

Feature Detection#

1/* Check for container query support */ 2@supports (container-type: inline-size) { 3 .container { 4 container-type: inline-size; 5 } 6} 7 8/* Fallback styles */ 9.card { 10 /* Mobile-first base styles */ 11} 12 13/* Media query fallback */ 14@media (min-width: 600px) { 15 .card { 16 display: flex; 17 } 18} 19 20/* Container query enhancement */ 21@supports (container-type: inline-size) { 22 .card-container { 23 container-type: inline-size; 24 } 25 26 /* Override media query */ 27 @container (min-width: 400px) { 28 .card { 29 display: flex; 30 } 31 } 32}

JavaScript Integration#

1// Check container query support 2if (CSS.supports('container-type', 'inline-size')) { 3 console.log('Container queries supported!'); 4} 5 6// ResizeObserver for additional logic 7const container = document.querySelector('.container'); 8const observer = new ResizeObserver(entries => { 9 for (const entry of entries) { 10 const width = entry.contentRect.width; 11 entry.target.dataset.size = width < 300 ? 'small' : 12 width < 600 ? 'medium' : 'large'; 13 } 14}); 15 16observer.observe(container);

Best Practices#

Design: ✓ Use inline-size for most cases ✓ Name containers for clarity ✓ Design components in isolation ✓ Start mobile-first Performance: ✓ Limit containment scope ✓ Avoid deep nesting ✓ Use container-type, not size ✓ Test with ResizeObserver Patterns: ✓ Responsive cards and widgets ✓ Adaptive grids ✓ Fluid typography with cqi ✓ Component-based responsive design Avoid: ✗ Over-using size containment ✗ Too many nested containers ✗ Forgetting fallbacks ✗ Querying unknown containers

Conclusion#

Container queries enable truly responsive components that adapt to their container, not the viewport. Use them for reusable components, dashboard widgets, and any element that appears in different contexts. Combine with container query units for fluid sizing and provide fallbacks for older browsers.

Share this article

Help spread the word about Bootspring