10
Containers
Viewport breakpoints can't fix a card that lives in both a sidebar and a full-width grid. Container queries can.
5 min read
You built a card component. It has a heading, a short description, and a metadata row. In the main product grid, at three columns wide, it looks right. In the sidebar, where it's narrower, the heading wraps badly and the metadata row overflows.
You wrote a media query at 768px. The sidebar card changed. So did every other card on the page, including the ones in the grid that looked fine.
Viewport breakpoints measure the wrong thing. They measure the window. The card doesn't care about the window. It cares about its own width.
Container Query
Typography scale for modern interfaces
A deep dive into modular type scales and how they create visual hierarchy across viewports.
Drag the slider to narrow the card. Below 340px the title shrinks and the metadata stacks vertically. Above 340px it gets a larger heading and a horizontal metadata row. The viewport doesn't change — only the container width does. This is what @container rules respond to.
The Rule
Add container-type: inline-size to the component wrapper. Query the wrapper, not the viewport.
.card {
container-type: inline-size;
}
/* Applies only when the card itself is at least 28rem wide */
@container (min-width: 28rem) {
.card-title {
font-size: var(--text-h3);
}
.card-meta {
flex-direction: row;
}
}.card {
container-type: inline-size;
}
/* Applies only when the card itself is at least 28rem wide */
@container (min-width: 28rem) {
.card-title {
font-size: var(--text-h3);
}
.card-meta {
flex-direction: row;
}
}container-type: inline-size makes the element a containment context. Any @container rule inside it measures that element's width, not the viewport. The card in the sidebar gets the narrow layout. The card in the grid gets the wide one. Same component. No conflict.
Naming containers
Nested components query the nearest ancestor containment context by default. If you need to query a specific ancestor, name it.
.sidebar {
container-type: inline-size;
container-name: sidebar;
}
@container sidebar (min-width: 16rem) {
.sidebar-title {
font-size: var(--text-h2);
}
}.sidebar {
container-type: inline-size;
container-name: sidebar;
}
@container sidebar (min-width: 16rem) {
.sidebar-title {
font-size: var(--text-h2);
}
}This is most useful when one component is nested inside another, and each has its own container context.
Container units
Inside @container blocks you can use cqi (container inline) and cqb (container block) units, which work like vw and vh but relative to the container.
@container (min-width: 24rem) {
.card-heading {
/* 4% of the card's inline size */
font-size: clamp(1rem, 1cqi + 0.8rem, 1.5rem);
}
}@container (min-width: 24rem) {
.card-heading {
/* 4% of the card's inline size */
font-size: clamp(1rem, 1cqi + 0.8rem, 1.5rem);
}
}Combining clamp() with cqi gives you fluid type that responds to the container width instead of the viewport width.
Common Mistake
Using container-type: size instead of container-type: inline-size. size also establishes block-axis containment, which requires knowing the element's height ahead of time. Most components don't have a defined height, so this breaks layout. Use inline-size.
Container queries are supported in Chrome 106+, Firefox 110+, Safari 16+. The cqi unit follows the same support table.