CSS layout with flexbox and grid
Most layout bugs come from not knowing how the browser decides a box’s size. Flexbox and grid both distribute space, but they start from different questions.
One dimension or two
Flexbox lays items out along a single axis. Content comes first: each item has a natural size, and flexbox shares out the leftover space (or takes it back) using flex-grow and flex-shrink. It suits toolbars, button rows and anything where the number of items varies.
Grid lays items out in rows and columns at the same time. Layout comes first: you define tracks, then place content into them. It suits page shells, card galleries and forms with aligned columns.
A useful test: if you find yourself setting fixed widths on flex items so that rows line up, you wanted grid.
The box model and sizing
Set box-sizing: border-box globally so width includes padding and border. Without it, a width: 100% box with padding overflows its parent.
Sizes are either extrinsic, set from outside (width: 20rem, 50%), or intrinsic, derived from the content:
min-contentis the narrowest the box can get without overflowing, usually the longest word or widest image;max-contentis the width the content wants with no wrapping;fit-contentbehaves likemax-contentbut never grows beyond the available space.
Grid adds the fr unit, a share of the free space left after fixed and intrinsic tracks are sized, and minmax(min, max), which gives a track a floor and a ceiling.
.page {
display: grid;
grid-template-columns: minmax(12rem, 16rem) minmax(0, 1fr);
gap: 1.5rem;
}
Responsive without breakpoints
Many layouts can adapt to their container without a single media query. This gallery fits as many columns of at least 16rem as the space allows, and stretches them to fill the row:
.cards {
display: grid;
grid-template-columns:
repeat(auto-fill, minmax(min(16rem, 100%), 1fr));
gap: 1rem;
}
The inner min(16rem, 100%) stops a single column overflowing on very narrow screens. Use auto-fit instead of auto-fill if you want a few items to stretch across the whole row rather than leave empty tracks at the end.
For type and spacing, clamp(min, preferred, max) scales smoothly between two limits:
h1 { font-size: clamp(1.75rem, 1.2rem + 2.5vw, 3rem); }
Keep a rem component in the preferred value so text still responds to the user’s font size setting. When a component needs to change shape depending on where it is placed, not on the viewport, use container queries rather than media queries.
Common layout bugs
The min-width blowout. Flex and grid items have min-width: auto by default, which in practice means they refuse to shrink below their min-content width. A long URL, a wide table or a <pre> block then pushes the item, and the whole page, wider than the screen. The fix is to let the item shrink:
.main { min-width: 0; } /* flex item */
In grid, 1fr means minmax(auto, 1fr), so the same thing happens. Write minmax(0, 1fr) for tracks that hold unpredictable content.
Overflow that hides the real problem. Adding overflow: hidden to the body silences horizontal scrolling but also clips focus rings and sticky elements. Find the element that is too wide instead: in DevTools, temporarily add * { outline: 1px solid red } and scroll sideways.
Long words. User-generated text needs overflow-wrap: anywhere (or break-word) so a long string wraps instead of overflowing.
Truncation that does nothing. text-overflow: ellipsis only works together with overflow: hidden and white-space: nowrap, and inside a flex item it also needs min-width: 0.
How to decide
- Content-sized items in a row or column: flexbox.
- Anything that must align in two directions: grid.
- Let content define sizes where you can; set fixed sizes only where the design needs them.
- Use
minmax(0, 1fr)andmin-width: 0wherever content is unpredictable. - Try
auto-fill,minmax()andclamp()before adding a breakpoint. - Test with long names, long URLs and a 320px wide viewport.