Skip to content

Container Queries

Container Queries allow components to respond to their container’s size rather than the viewport, enabling truly reusable responsive components.

/* Media queries respond to viewport only */
.card {
display: flex;
flex-direction: column;
}
.card-image {
width: 100%;
}
@media (min-width: 600px) {
.card {
flex-direction: row;
}
.card-image {
width: 200px;
}
}
/* Problem: Same card in a narrow sidebar
still gets the "wide" layout because
viewport is wide, not the container */
// JavaScript workaround
const observer = new ResizeObserver((entries) => {
for (const entry of entries) {
const width = entry.contentRect.width;
entry.target.classList.toggle('wide', width > 400);
}
});
observer.observe(document.querySelector('.card-container'));

Problems:

  • Media queries only respond to viewport size
  • Same component behaves differently based on page layout
  • JavaScript required for container-aware styling
  • Components not truly portable between contexts
  • Resize observers add complexity and performance overhead
/* Define a containment context */
.card-container {
container-type: inline-size;
container-name: card;
}
/* Shorthand */
.card-container {
container: card / inline-size;
}
/* Component responds to container size */
.card {
display: flex;
flex-direction: column;
}
.card-image {
width: 100%;
}
@container card (min-width: 400px) {
.card {
flex-direction: row;
}
.card-image {
width: 200px;
}
}

Benefits:

  • Components respond to their container, not viewport
  • Truly reusable components across different contexts
  • No JavaScript required
  • Works in sidebars, modals, grids—anywhere
  • Declarative CSS solution
.card-container {
container-type: inline-size;
}
.card-title {
/* cqi = 1% of container's inline size */
font-size: clamp(1rem, 5cqi, 2rem);
}
.card-description {
/* Container query units */
padding: 2cqi;
}
UnitDescription
cqw1% of container’s width
cqh1% of container’s height
cqi1% of container’s inline size
cqb1% of container’s block size
cqminSmaller of cqi or cqb
cqmaxLarger of cqi or cqb
.card-container {
container: card / inline-size;
}
@container card (min-width: 300px) {
.card {
padding: 16px;
}
}
@container card (min-width: 500px) {
.card {
padding: 24px;
gap: 16px;
}
}
@container card (min-width: 700px) {
.card {
padding: 32px;
gap: 24px;
}
}
.sidebar {
container: sidebar / inline-size;
}
.widget {
container: widget / inline-size;
}
/* Widget responds to sidebar */
@container sidebar (max-width: 300px) {
.widget {
font-size: 14px;
}
}
/* Widget content responds to widget */
@container widget (min-width: 200px) {
.widget-content {
display: grid;
grid-template-columns: 1fr 1fr;
}
}
.card-container {
container-type: inline-size;
--theme: light;
}
.card-container.dark {
--theme: dark;
}
/* Query custom property values */
@container style(--theme: dark) {
.card {
background: #1a1a1a;
color: white;
}
}

Supported in all modern browsers. See caniuse.com/css-container-queries.

Style container queries have more limited support. See caniuse.com/css-container-queries-style.

  • container-type: inline-size creates layout containment
  • Containment prevents certain layout calculations from propagating
  • Generally good performance, but avoid deeply nested containers
  • Container query units are calculated at layout time

Use Container Queries when:

  • Building reusable components that appear in different contexts
  • Component layout should depend on available space, not viewport
  • Creating widget systems for dashboards
  • Building card components for grids of varying column counts
  • Components may appear in sidebars, modals, or main content

Keep using Media Queries when:

  • Responding to viewport-level changes
  • Changing overall page layout
  • Handling print stylesheets
  • Adjusting for device capabilities (hover, pointer)