Grid or flexbox
The distinction that resolves most confusion is dimensionality. Flexbox lays out along one axis — a row or a column — and is the right tool when content size should drive the arrangement. Grid works in two dimensions simultaneously and is right when you want to define a structure that content fits into.
In practice: navigation bars, button groups, toolbars, and anything that should wrap naturally are flexbox problems. Page layouts, card galleries with aligned rows and columns, and any design with a defined structure are grid problems. They compose freely — a grid cell commonly contains a flex container.
The one-dimensional limitation of flexbox is concrete. In a wrapped flex container, each line sizes independently, so items in the second row do not align with those in the first. If column alignment across rows matters, that is grid.
Grid features worth knowing
minmax() combined with auto-fit or auto-fill produces responsive layouts with no media queries at all: repeat(auto-fit, minmax(250px, 1fr)) fits as many 250-pixel-minimum columns as the space allows and distributes the remainder equally. This single line replaces what used to take several breakpoints.
The difference between auto-fit and auto-fill shows only when there are fewer items than columns. auto-fill keeps the empty tracks, so items stay at their minimum width on the left; auto-fit collapses them, so items stretch to fill the row. Neither is wrong — collapsing usually looks better for a card grid, keeping is better where column alignment must stay stable.
The fr unit distributes free space after fixed sizes and gaps are accounted for, which is why 1fr is not the same as 50 percent in a two-column grid with a gap. Named grid areas via grid-template-areas make layouts genuinely readable, since the CSS shows the shape of the page, and rearranging at a breakpoint is a matter of redrawing the map.
Modern responsive layout
Container queries changed the model significantly. Media queries respond to viewport size, so a component cannot know whether it is in a wide main column or a narrow sidebar. A container query responds to the size of the component's own container, which makes genuinely reusable components possible — a card that switches to a horizontal layout when it has room, wherever it is placed.
Two smaller things solve long-standing problems. gap works on flexbox as well as grid, removing the need for negative margins on the container to offset child margins. And logical properties — margin-inline, padding-block, inset-inline-start — adapt automatically to writing direction, so a layout works in right-to-left languages without a separate stylesheet.
Two habits prevent the most common layout bugs: set box-sizing: border-box globally so padding and border are included in declared widths, and use min-width: 0 on flex and grid children that contain long text. Flex items default to a minimum size based on content, which is why a long unbroken string can push a layout wider than its container — this is the cause of most mysterious horizontal scrollbars.