CSS Tutorial / CSS Container Queries for Reusable Components
Container size queries let a component respond to an eligible ancestor container's size rather than only the viewport. Declare a query container with container-type, then use @container to change descendant styles at the appropriate size.
Compare viewport and container conditions
Even if the browser width is 1200px, proper placement will vary depending on whether the card is in the 700px body text area or the sidebar 300px area. It is difficult to express this difference using only the CSS Media Queries and Content-Based Breakpoints, where the two cards share the same viewport conditions. When reusing parts, you can reduce layout-specific exception rules by changing their internal configuration based on the parent width.
| division | Judgment criteria | appropriate role |
|---|---|---|
| @media | Environment such as viewport width | Number of columns across page, sidebar location |
| @container | size of selected ancestor container, etc. | Placement of pictures and text inside the card, spacing between parts |
Container queries also compare the width of the conditional expression rather than “lay out horizontally if the screen is horizontal.” When the sidebar grows larger than 30rem, the cards within it can also be laid out horizontally. If you always want a narrow sidebar, you should also specify a maximum width in the parent layout.
Declare a size query container
.slot { container: panel / inline-size; }
@container panel (min-width: 30rem) {
.card { display: grid; grid-template-columns: 8rem minmax(0, 1fr); gap: 1rem; }
}
container: panel / inline-size declares the name panel and inline-size containment. In horizontal writing, the inline dimension is horizontal; vertical writing changes that correspondence. Supply the container's width through its outer block layout or Grid track so that the card has a meaningful size to query. See CSS Grid Basics: Tracks, Lines, and Item Placement.
The parent to evaluate the condition is .slot and the descendant to receive the conditional style is .card. Do not write in a structure that changes the card itself by looking up the size of the card itself. To avoid the problem of conditions being flipped over and over again as the size changes, size queries operate relative to appropriate ancestors.
inline-size containment makes the container's inline size independent of its descendants' content contributions. A shrink-to-fit container can therefore become unexpectedly small. Here, a block or Grid track supplies its width. Using size containment also affects the other axis and can collapse a content-sized height, so choose the containment deliberately. For further detail, see You can see the extent of size isolation in MDN's container-type description.
Reuse a card in different parent widths
Save it as container-card.html and open it in your browser. The color box is an example element that represents the picture area, so there is no separate image file. On larger screens, separate the main text and a sidebar of up to 20rem, and on 44rem or less, place the two areas in one column.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>container-card.html</title>
<style>
*, *::before, *::after { box-sizing: border-box; }
body { margin: 0; font: 1rem/1.6 sans-serif; color: #243447; }
h1, h2, p { margin-top: 0; } a { color: #145f8f; }
a:focus-visible, button:focus-visible, input:focus-visible { outline: 3px solid #ac4100; outline-offset: 3px; }
.layout { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 20rem); gap: 1rem; width: min(100%, 75rem); margin-inline: auto; padding: 1rem; }
.slot { min-width: 0; container: panel / inline-size; }
.card { min-width: 0; padding: 1rem; background: #e8f3fa; overflow-wrap: anywhere; }
.media { min-height: 7rem; background: #287699; }
.card h2 { font-size: 1.25rem; } .card p { margin-bottom: 0; }
@container panel (min-width: 30rem) {
.card { display: grid; grid-template-columns: 8rem minmax(0, 1fr); gap: 1rem; align-items: start; }
}
@media (max-width: 44rem) { .layout { grid-template-columns: minmax(0, 1fr); } }
</style>
</head>
<body>
<div class="layout">
<main class="slot"><article class="card"><div class="media" aria-hidden="true"></div><div><h2>Card in the main content area</h2><p>The image and text appear side by side when the parent is at least 30rem wide.</p></div></article></main>
<aside class="slot"><article class="card"><div class="media" aria-hidden="true"></div><div><h2>The same card in a sidebar</h2><p>The sidebar in this example stays at or below 20rem even on a wide screen, so the card keeps its vertical layout.</p></div></article></aside>
</div>
</body>
</html>
In a default environment where the initial font size is 16px, 30rem is 480px. If the body parent on a large screen is 480px or larger, the inside of the card will be two columns, and in a sidebar that is up to 320px, the inside of the card will be arranged vertically. On narrow screens, as the sidebar moves down, its parent may become wider. Therefore, it is normal for both cards to be placed horizontally on a 600px screen as long as the parent width satisfies the conditions.
The outer condition of 44rem and the inner condition of 30rem solve different problems. The first condition determines how to place the area on the entire page, and the second condition determines how to place pictures and text within the space given to each card. Even if the parents' names are the same, each card finds a panel among its ancestors that matches the conditions.
Choose nearest and named containers
An unnamed size query selects the closest ancestor container whose conditions can be queried. Even if the outer area is wide, the results may vary if a narrower container is added in the middle. You can see this difference separately by saving the following example as container-nested.html.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>container-nested.html</title>
<style>
*, *::before, *::after { box-sizing: border-box; }
body { margin: 0; font: 1rem/1.6 sans-serif; color: #243447; }
h1, h2, p { margin-top: 0; } a { color: #145f8f; }
a:focus-visible, button:focus-visible, input:focus-visible { outline: 3px solid #ac4100; outline-offset: 3px; }
main { width: min(100%, 42rem); padding: 1rem; }
.outer { width: 100%; container: outer / inline-size; border: 2px solid #789; padding: 1rem; }
.inner { width: min(100%, 17rem); container-type: inline-size; }
.card { padding: 1rem; background: #e8f3fa; overflow-wrap: anywhere; }
.media { min-height: 5rem; background: #287699; }
@container (min-width: 30rem) {
.card { display: grid; grid-template-columns: 5rem minmax(0, 1fr); gap: 1rem; }
}
</style>
</head>
<body>
<main><h1>Which parent's width is queried?</h1><div class="outer"><div class="inner"><article class="card"><div class="media" aria-hidden="true"></div><p>The nearest inner container has a maximum of 17rem.</p></article></div></div></main>
</body>
</html>
On wide screens, the outer container is larger than 30rem, but the inner container is at most 17rem. The unnamed condition selects the closest inside, so the cards are laid out vertically. Keep the other code and replace @container (min-width: 30rem) with the following:
@container outer (min-width: 30rem) {
.card { display: grid; grid-template-columns: 5rem minmax(0, 1fr); gap: 1rem; }
}
Now we select the outer ancestor named outer, so the card is laid out horizontally on the wide screen. The card itself is still within the 17rem maximum, so you'll need to check separately whether the body width is sufficient. The name is not a property that automatically corrects incorrect standards, but a function that specifies which ancestor to use as the standard. After comparing, restore to the unnamed condition.
Diagnose queries that do not apply
- Checks whether the target of the conditional selector is a descendant of the container. Check that you are not trying to apply the same style to the parent itself.
- Check the parent's container-type and actual content width in developer tools. There is no confusion between the outer width, including padding and border, and the size to be searched.
- Check to see if a new container has been created, and if necessary, specify the criteria with container-name.
- If you change only the container-type to normal, you can see the difference in that the size query is not applied. After confirmation, restore to inline-size.
Keep a readable vertical card as the baseline, then add horizontal placement when the container query applies. The baseline also serves browsers that do not support the size query. Check support and behavior in the browsers your readers actually need. See MDN: Container size and style queries.










