A Card Does Not Know Where It Was Placed
In one line
flex divides one axis and grid sets two axes at once. And the real question of responsiveness is not "how many px is the screen" but "how many px is this box," and the thing that answers that question is the container query.
Why this was needed
You build a card component. To make it lay out vertically when narrow and horizontally when wide, you use a media query.
.card { display: block; }
@media (min-width: 700px) { .card { display: flex; } }
Place this card in the body and it works well. But place the same card in a narrow sidebar and it breaks. The screen is 1400px, so the media query is true, but the box the card sits in is 280px.
A component doesn't know where it is placed. A media query asks only about the screen width, but what a component needs to know is the width of its own box. This mismatch has been the most frequent structural problem in design systems.
How it works — flex and grid
Which of the two to use is decided not by taste but by dimension. MDN's comparison documentation sums up this relationship.
| Situation | The right one |
|---|---|
| Dividing within one row (or one column) | flex |
| Distributing automatically according to content size | flex |
| Deciding both rows and columns | grid |
| Screen skeleton — header, side, body, footer | grid |
| Places must be kept even when the count changes | grid |
flex starts from the content. Items decide their own size and divide what is left over or lacking. grid starts from the frame. It decides the cells first and puts items into those cells.
So for a "single row where you don't know what will go in," like a toolbar or a row of buttons, flex is right, and for something "where the cells are decided first," like a page skeleton, grid is right.
The problem of flex items that won't shrink
There is a trap you meet most often in flex. An item containing long text or a wide table doesn't shrink and pushes out the container.
The cause is that the default min-width of a flex item is auto. This value means "never get smaller than the content's minimum size," so if there is a long string that can't wrap, it always takes up at least that much. MDN's flex ratios documentation explains this behavior.
.item { min-width: 0; } /* 또는 overflow: hidden */
This one line is very often the answer to "why is there a horizontal scrollbar."
flex: 1 is shorthand for flex-grow: 1; flex-shrink: 1; flex-basis: 0%. Because of flex-basis: 0%, it ignores content size and divides the remaining space equally. flex: auto has flex-basis: auto, so it starts from the content size. If you mix the two, you get results different from what you expected.
Placing with grid without counting lines
grid has an idiom that responds without a media query.
.cards {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
gap: 16px;
}
It means "make as many columns of at least 240px as fit, and divide the leftover space equally." The number of columns changes by itself without counting screen widths. CSS Grid 2 defines the difference between auto-fit and auto-fill — auto-fill keeps empty columns, and auto-fit collapses empty columns so the remaining items grow.
Container queries
There is still a problem left. The idiom above changes the number of columns but can't change the layout inside the card. If the inner structure must differ when the card is 240px and when it is 600px, the card has to ask about its own width.
.card-wrap { container-type: inline-size; }
@container (min-width: 420px) {
.card { display: grid; grid-template-columns: 120px 1fr; }
}
container-type: inline-size is a declaration that "I will make this element's inline-direction size the target of queries." Then its descendants can ask about the ancestor's width with @container. It is defined by CSS Containment 3, and MDN's container queries documentation sums up the usage.
Remember two things.
- It can't ask about itself. You put the rule not on the element declared as the container but on an element inside it. So you need one layer of wrapper
container-type: inline-sizeturns on size containment. In that direction, children no longer affect the parent's size, so you can get unexpected results where the height used to follow the content
What it looks like in the field
Three things I have actually run into.
First, the same component breaks in different ways in two places. It is fine in the body and breaks only in the sidebar. This is almost always a case where a container query was needed in a spot where a media query was used.
Second, a horizontal scrollbar appeared and you can't find the culprit. The most common cause is the flex item's min-width: auto, and the next is width: 100vw (which includes the scrollbar width).
Third, there are more than ten media query breakpoints. This is usually a sign that the component is asking about the screen width when it has no reason to. If you move to repeat(auto-fit, minmax(...)) and container queries, most of the breakpoints disappear.
What to check in the next quiz
You check the split between flex and grid, the difference between flex: 1 and flex: auto, the overflow that min-width: auto creates, the difference between auto-fit and auto-fill, and how container queries differ from media queries.