CSS Container Queries, supported in all major browsers since Chrome 105, Firefox 110, and Safari 16 (late 2022), allow an element to respond to the size of its containing block rather than the viewport width, solving the long-standing problem of component-level responsiveness. A container query requires two declarations: the parent element must opt in as a containment context with container-type: inline-size (or size for both axes), and optionally assign a name with container-name; child elements then use @container rules with width conditions such as (min-width: 400px). Without container queries, a card component placed in a narrow sidebar required completely separate media-query breakpoints from the same card placed in a wide main column — developers either duplicated CSS or accepted layout regressions in one context. Container queries eliminate that duplication: the component carries its own responsive rules regardless of where it is placed in the layout. The container-type: inline-size value establishes containment only on the inline axis (horizontal in LTR), which is the common case; container-type: size requires the container to have an explicit block size and is useful for height-responsive layouts. Container query length units — cqw, cqh, cqi, cqb, cqmin, cqmax — work like viewport units but relative to the query container, enabling fluid typography and spacing that scales with the component’s own width. Named containers allow a deeply nested element to skip intermediate containers and query a specific ancestor by name — useful when a component needs to respond to the page section width, not the immediate parent width. The @container style() function (still behind a flag as of early 2023) will enable style queries, allowing elements to respond to custom property values on their container. Container queries compose naturally with CSS Grid and Flexbox: grid children become containment contexts, and their own children use container queries to adapt, creating a fully self-contained responsive system at every nesting level.
Problem: A card component placed in a narrow sidebar breaks its layout because media queries respond to viewport width, not to the container width — requiring duplicated breakpoints or accepting visual regressions in one context.
Solution: Declare container-type: inline-size on the card wrapper, use @container rules on child elements to adjust layout at component breakpoints, and assign container names to query specific ancestors from deeply nested children.
/* Step 1: opt the card wrapper into containment */
.card-wrapper {
container-type: inline-size;
container-name: card;
}
/* Step 2: default (narrow) layout */
.card {
display: grid;
grid-template-columns: 1fr;
gap: 1rem;
}
.card__image {
aspect-ratio: 16 / 9;
width: 100%;
object-fit: cover;
}
/* Step 3: side-by-side when container >= 480px */
@container card (min-width: 480px) {
.card {
grid-template-columns: 200px 1fr;
align-items: start;
}
.card__image {
aspect-ratio: 1;
border-radius: 4px;
}
.card__body {
padding: 0 0 0 1rem;
}
}
/* Step 4: fluid typography with container query units */
@container card (min-width: 320px) {
.card__title {
font-size: clamp(1rem, 3cqi, 1.5rem);
}
}
/* Named containers — query a specific ancestor, skipping intermediate containers */
.page-section {
container-type: inline-size;
container-name: section;
}
.card-wrapper {
container-type: inline-size;
container-name: card;
}
/* Queries the section container (skips the card container) */
@container section (min-width: 900px) {
.card { grid-template-columns: repeat(3, 1fr); }
}
/* Queries the immediate card container */
@container card (min-width: 480px) {
.card { grid-template-columns: 160px 1fr; }
}
NOTE: Setting container-type on an element creates layout, style, and size containment for that axis — percentage heights on children of an inline-size container will not resolve against the container height. Use container-type: size only when you explicitly need height containment and can guarantee the container has a defined block size.