The Current: Flow, Flex and Grid
A page isn't a canvas you place things on — it's a current that arranges them. Learn to describe relationships instead of coordinates and the layout survives content you didn't plan for.
Everyone coming from a design tool makes the same first move: position everything absolutely, at the coordinates from the mockup. It looks perfect. Then a product name turns out to be four words instead of two, and the whole thing falls apart — text overlapping, boxes colliding, a scrollbar where none should be.
The mistake isn't in the pixel values. It's a category error. In a design tool you place objects on a canvas; in a browser you describe relationships and the browser does the arithmetic — with real content, at a width you don't know, in a font that may not have loaded, in a language that may be a third longer.
A layout isn't drawn, it's described. You state the relationships; the browser does the arithmetic.
Normal flow is already a layout
Before any of the tools, the default: boxes stack down the page, each taking the full available width, in source order. Inline things — text, links, images inside text — flow along a line like words, wrapping when they run out of room.
That's the normal flow, and it's worth respecting rather than replacing. An unstyled HTML document is already responsive: it fills any width, it never overflows, it reflows when the text grows. Every layout technique below is an adjustment to the flow, not an escape from it. Most of the pain in frontend comes from people fighting the current instead of steering it.
Which is why the two habits below are worth adopting before anything else. Don't set heights. Width is yours to decide; height belongs to the content, and a fixed height is a promise that the text will never be longer than you imagined. Let content push. If a box has to be a certain size for the design to work, the design has an assumption in it that real data will break.
Flexbox: one axis at a time
Flexbox arranges children along a single line — a row or a column — and shares out the space between them.
The mental model is two axes. You pick the main axis with flex-direction (row or column), and the perpendicular one is the cross axis. Then two properties do most of the work: justify-content distributes children along the main axis, align-items positions them across the cross axis. Everyone confuses these two for a while; the cure isn't memorising which is which, it's remembering that one is "along" and the other is "across", and that swapping the direction swaps their meaning too.
The children get a say in how space is divided: flex-grow says take a share of the leftover space, flex-shrink says give up space when there isn't enough, flex-basis is the size to start from. And gap puts space between them — use it instead of margins on children, which is the older technique and always leaves an unwanted margin on the last one.
Flex is the right tool when you're thinking in one direction: a toolbar, a row of buttons, a label next to a control, a card's contents stacked vertically, anything that should wrap onto the next line when it runs out of room.
Grid: both axes at once
Grid lets the parent define rows and columns first, and place children into them.
The unit that makes it click is fr — a fraction of the free space. Three columns of 1fr 2fr 1fr split the row into quarters, after gaps are taken out, with no percentages and no arithmetic on your part. repeat() saves typing, named template areas let you draw the layout as text in your stylesheet, and minmax() sets a floor and ceiling for a track.
The combination worth learning early, because it does an enormous amount of work for free: a grid with columns of repeat(auto-fit, minmax(220px, 1fr)) fits as many columns as will hold 220 pixels each and reflows to fewer as the window narrows — a responsive card grid with no media queries and no breakpoints at all.
Grid is the right tool when two dimensions matter at once: page skeletons, dashboards, card galleries, forms with aligned label and field columns, anything where things must line up both across and down.
Choosing between them
The question is genuinely this short: one dimension or two?
Flex is content-out — the children's sizes drive the result, and you distribute what's left. Grid is layout-in — you define the structure, and the children fit into it. If you find yourself fighting flex with widths and wrappers to make things line up in both directions, you wanted grid. If you're defining a one-column grid to stack three things, you wanted flex.
And they aren't rivals. The normal shape of a real page is grid for the skeleton, flex inside the components, with the two nested happily. Every flex or grid container starts a new context for its own children only, so you compose them all the way down.
Positioning: the escape hatch
Everything above is flow. position is how you step outside it, and it has five values worth knowing:
- static — the default, in the flow.
- relative — still in the flow and still occupying its space, but nudged visually. Its main use in practice is to become an anchor for something absolute inside it.
- absolute — removed from the flow entirely, positioned against the nearest positioned ancestor. It no longer takes up space, so nothing below it moves.
- fixed — positioned against the viewport, staying put while the page scrolls.
- sticky — in the flow until it hits a scroll threshold, then pinned. A section header that sticks to the top of the list.
Use these for things that genuinely sit on top of the layout: dropdowns, tooltips, badges on an avatar, modal overlays, sticky headers. Using them for the layout itself is the mistake this article opened with.
While you're here, one related trap: z-index doesn't compare across the whole page. Elements live in stacking contexts, created by a positioned element with a z-index — and also, unexpectedly, by transform, opacity below 1, filter and a few others. A child of one context can never rise above a sibling context, no matter how large its z-index. "z-index: 9999 and it's still behind that thing" is always this, and the fix is finding which ancestor created the context, not adding another nine.
Build the dozen, don't read about the dozen
There are roughly a dozen layouts you will build for the rest of your career: centred content column, sidebar with main area, sticky header with scrolling body, card grid, toolbar with items pushed to both ends, form with aligned labels, media object (image beside text), footer pinned to the bottom of a short page, modal overlay, two-column that becomes one on mobile, table that scrolls horizontally, list with equal-height items.
That list is your chunk inventory — the How to Learn sphere's term for the fused blocks a skill is actually made of. An experienced developer meeting "sidebar and main, sidebar collapses on mobile" doesn't reason it out from first principles; they recall a block. You build those blocks the only way blocks are ever built: by making each one several times, in different contexts, with the result checked.
Two details make the difference between practice that works and practice that flatters you. Interleave — don't build six card grids in a row; alternate the types, because real work never tells you which technique the situation needs, and choosing the technique is the skill. And space the repeats — building the same layout again three days later, from scratch, without looking at last time's code, is worth more than an hour of building it once. The struggle to remember is the mechanism, not a sign you've failed.
In practice
Say "one axis or two?" out loud before writing CSS. Every time, for a month. It's the single decision this whole article exists to make automatic.
Delete every fixed height you write. Then find out what actually needed constraining. Nine times out of ten it was nothing.
Resize the window constantly while building, not at the end. A layout that only works at your window width isn't a layout, it's a screenshot.
Turn on the devtools grid and flex overlays. They draw the tracks, gaps and alignment lines directly on the page. This is the fastest feedback loop you'll get, and it turns "it's off by a bit and I don't know why" into a visible answer.
Use gap, not margins on children. Fewer edge cases and no orphan margin at the end.
Rebuild the dozen, spaced and mixed. One layout per session, three variants of it, then a different type next session, and the same one again a week later from memory. Six weeks of that is worth more than any course.
Check yourself
Close the article and answer in your own words:
- Why is placing things by absolute coordinates the wrong mental model for a page?
- What is normal flow, and why is an unstyled document already responsive?
- What's the difference between the main axis and the cross axis, and which properties act on each?
- Give the one-sentence rule for choosing between flex and grid.
- What does
1frmean, and what doesauto-fitwithminmaxbuy you? - When is
position: absolutethe right choice, and when is it a symptom? - Why doesn't a huge
z-indexalways win?
In short
- A page is described, not drawn: you state relationships and the browser computes the geometry against real content at an unknown width.
- Normal flow is already a working responsive layout; every technique is an adjustment to it. Don't set heights, and let content push.
- Flex works along one axis:
justify-contentalong the main axis,align-itemsacross the cross axis, with children negotiating via grow, shrink and basis. - Grid defines rows and columns up front;
fr,repeat, template areas, andauto-fitwithminmaxgive responsive grids with no media queries. - Choose by asking whether you care about one dimension or two, and nest them: grid for the page, flex inside components.
positionis the escape hatch for things sitting on top of the layout — dropdowns, overlays, sticky headers — not for the layout itself.z-indexonly compares within a stacking context, andtransform,opacityandfiltercreate them unexpectedly.- About a dozen layouts recur forever; build them repeatedly, mixed rather than blocked, and spaced across days.