This page starts with the single box: its parts, how its size is measured, and the spacing around it. Then it widens out to the algorithms that place boxes relative to one another: normal flow, positioning, flexbox and grid. It ends by building the same classic page layout twice, once with flexbox and once with grid, as live demos you can open and change.
1. Layout as an algorithm
Most CSS properties configure one box: its color, its font, its padding. Layout properties configure relationships between boxes. To turn those relationships into positions and sizes, the browser runs a layout algorithm at each level of the box tree, and which algorithm it runs depends on the parent's display value.
Boxes, not elements
The browser lays out a box tree, not the DOM. The two are close but not identical. Most elements generate exactly one box. An inline element that wraps across lines generates a fragment on each line. display: none generates no box at all, and display: contents generates none for the element itself while its children take part in the parent's layout. The pseudo-elements ::before and ::after add boxes that have no element behind them.
The four primary algorithms
| Algorithm | Container | Dimensions | Best for |
|---|---|---|---|
| Normal flow | display: block (the default) | One: blocks stack, inline content wraps in lines | Documents: prose, headings, lists, figures |
| Flexbox | display: flex | One axis, chosen by the author | A row or column of items sharing space: toolbars, nav bars, button groups |
| Grid | display: grid | Two: rows and columns together | Page layouts, card grids, dashboards, forms with aligned labels |
| Subgrid | A grid item with grid-template-rows: subgrid or columns | Borrows the parent grid's tracks | Lining up the insides of separate cards or components |
Each algorithm answers the same question with different inputs: given a container with some available space, and children with sizing and placement properties, where should each child go and how big should it be? Normal flow asks each child for its size and stacks them. Flexbox asks for flex factors and shares out the space along one axis. Grid solves a two-dimensional system of tracks. Layout is constraint solving, and choosing a method means choosing the vocabulary that fits the problem.
2. The box model and box-sizing
Every box has four concentric areas. The content holds the text or the children. Padding surrounds the content and shows the box's background. The border is drawn around the padding. The margin is transparent space outside the border that keeps other boxes away: it is space, not surface, and the background stops at the border edge.
inline-size measures depends on box-sizing.Sizing in logical terms
The size properties come in physical and logical pairs. inline-size is the size along the line (the width, in English), block-size the size along the stacking direction (the height). Each has min- and max- forms. They are widely available, and they keep a component correct when its writing mode changes, so this section uses them.
What a size measures
Set inline-size: 20rem, add 1rem of padding and a 1px border, and the question is whether the box is 20rem wide or a little over 22rem. The answer depends on box-sizing:
| Value | The size sets | Padding and border |
|---|---|---|
content-box (the initial value) | The content area | Added outside it, so the box grows past the size you set |
border-box | The area out to the border edge | Taken from inside it, so the content shrinks to make room |
Early Internet Explorer measured boxes the border-box way while the specification said content-box, and authors wrote brittle "box model hacks" to serve different numbers to different browsers. The box-sizing property ended the argument by letting the author choose, and nearly every modern style sheet chooses border-box in its reset layer: the size you set is the size you see.
Borders
A border has a width, a style and a color, and the border shorthand sets all three: border: 1px solid var(--rule). A border with no style (solid, dashed, double and so on) is not drawn at all. Borders do not inherit. border-radius rounds the corners, which once took sliced images and later vendor prefixes, and is now plain, widely available CSS.
Logical forms exist for every side: border-block-end draws a rule under a heading in any writing mode, and border-inline-start draws the accent bar on a quotation on whichever side the text begins.
3. Margins, padding and logical properties
Margin and padding take one to four values. The four-value form runs clockwise from the top: top, right, bottom, left. Missing values copy the opposite side, so margin: 1rem 2rem means 1rem above and below and 2rem on each side. Margins may be negative, which pulls a box beyond its normal position; padding may not.
The deck this course grew from teaches the four physical sides individually, and it is worth seeing what that looks like, because it is still how most of the CSS on the web is written:
Why logical properties matter
margin-left assumes a left-to-right language. Translate the page into Arabic or Hebrew and the inline direction flips, but a physical margin stays on the left, now on the wrong side of the text. The logical properties name the side by its role in the flow instead of by its place on the screen:
| Physical | Logical | Follows |
|---|---|---|
margin-top, margin-bottom | margin-block-start, margin-block-end | Writing mode |
margin-left, margin-right | margin-inline-start, margin-inline-end | Writing mode and direction |
| Top and bottom together | margin-block, padding-block | Both block sides at once |
| Left and right together | margin-inline, padding-inline | Both inline sides at once |
border-left and the rest | border-inline-start, border-block-end, … | The same |
width, height | inline-size, block-size | Writing mode |
top, right, bottom, left | inset-block-start, inset-inline-end, …, and the inset shorthand | The same |
That card is correct in English, Arabic and vertical Japanese with no overrides: the accent bar always marks the side where reading begins. Writing logical properties from the start costs nothing; converting a physical style sheet later means rewriting every spacing rule. Keep physical directions for things that really are physical, such as a drop shadow that suggests light from above.
Centering a block
An auto inline margin takes up the leftover space, so a block with a maximum size and margin-inline: auto sits in the middle of its container. That one pair of declarations makes the classic centered reading column, with no layout system involved.
Margin collapse
In normal flow, adjacent block-direction margins do not add up. They collapse, and the larger one wins. A paragraph with margin-block-end: 1rem followed by one with margin-block-start: 1.5rem ends up 1.5rem apart, not 2.5rem. This was designed for prose, where authors wanted one paragraph gap rather than two, and it surprises everyone who expects space to add.
- Sibling margins collapse: the larger wins.
- A first or last child's margin collapses through its parent when no border or padding separates them, so the child's margin appears outside the parent.
- Inline margins never collapse, and margins inside flex and grid containers never collapse.
To stop a parent's margins from collapsing with its children, give it display: flow-root, which starts a new block formatting context (a region whose insides lay out independently of the outside). Better still, for spacing between components, use a flex or grid container with gap and leave the collapse rules to the prose they were designed for.
4. display, outer and inner
The display property does two jobs. Its outer type says how the box takes part in its parent's layout: as a block that starts a new line, or inline within a line. Its inner type says which algorithm the box runs for its own children. display: flex is shorthand for "block outside, flex inside", and the two-value syntax spells that out: display: block flex. The two-value form is widely available, but the single keywords remain more common and read fine.
| Value | Outer | Inner | Use it for |
|---|---|---|---|
block | block | flow | The default for p, section, ul |
inline | inline | flow | The default for a, em, span |
inline-block | inline | flow-root | A box that sits in a line but takes a size and block padding |
flow-root | block | flow-root | A block that contains its floats and stops margin collapse |
flex, inline-flex | block, inline | flex | One-dimensional layout of the children |
grid, inline-grid | block, inline | grid | Two-dimensional layout of the children |
list-item | block | flow, plus a marker | The default for li |
contents | no box of its own | Letting a wrapper's children join the grandparent's layout | |
none | no box at all | Removing the element from layout and from the accessibility tree | |
Values such as run-in and marker, which appear in older references, never became reliable across browsers and are not used today.
Changing display does not change meaning
Setting display: block on a link changes its box, not its role: it is still a link to a screen reader and to the keyboard. The reverse caution applies to display: contents. Browsers have had bugs where an element given display: contents lost its semantics in the accessibility tree, so test it with assistive technology before using it on buttons, lists, tables or headings.
5. Normal flow, floats and positioning
Normal flow is the layout every page has before any layout is written: block boxes stack in the block direction and fill the inline size, inline boxes run along lines and wrap. It reflows for any screen, any zoom level and any text size, which makes it the right layout for most of a document. The first demo has no layout system at all. Its only layout rules are max-inline-size and margin-inline: auto on the article.
Floats: for wrapping text
A floated box is shifted to the start or end of its line and taken out of normal flow. The line boxes of the content that follows get shorter so that text wraps around it. That is what floats were designed for: a figure or a pull quote with prose flowing beside it, the way magazines set images. The clear property makes an element start below any floats before it, and a container with display: flow-root grows to contain its floats.
For about fifteen years, floats were also how the web built columns, because nothing else could put two blocks side by side. Every float layout needed a "clearfix" so containers would wrap their floated children, equal-height columns took tricks, and source order was bent to suit the floats. That is the pattern to recognize in old code and not to write:
Floats are for wrapping text around something. Page layout belongs to flexbox and grid, which the rest of this page covers.
Positioning
The position property changes how a box relates to the flow, and the inset properties (inset-block-start, inset-inline-end and their shorthand inset) move it.
| Value | In flow? | Offsets are measured from |
|---|---|---|
static (default) | Yes | Offsets are ignored |
relative | Yes: its original space is kept | Its own normal position |
absolute | No: siblings act as if it were not there | The nearest positioned ancestor, or the initial containing block |
fixed | No | The viewport; it stays put while the page scrolls |
sticky | Yes, until a scroll threshold | Its scroll container; it sticks while its own container is in view |
The common pairing is a relative parent with an absolute child. The relative parent does not move; it only becomes the containing block that the absolute child measures against. In the demo, the tag is pinned to the block-start, inline-end corner of the figure, takes up no space in the text, and overlaps whatever is beneath it.
Older tutorials reach for positioning often. Use it when the goal really is to take something out of the flow (a badge on a card, a dropdown menu, a skip link) or to fix it in place, as with sticky table headers. For arranging the main regions of a page, absolute positioning breaks as soon as the content, the text size or the screen changes; flexbox and grid adapt.
Anchor positioning, which tethers a positioned box to another element with anchor-name and position-anchor, is the modern answer for tooltips and popovers. It has reached the major engines only recently, so check its Baseline status and treat it as an enhancement over a working fallback.
Stacking and z-index
When boxes overlap, later boxes in the source paint over earlier ones. z-index changes that order for positioned boxes and for flex and grid items. Leave gaps between values (10, 20, 30) so a new layer can slot between two existing ones. The subtler rule is that z-index only compares boxes within the same stacking context. Properties such as opacity below 1, transform, and a positioned box with a z-index each start a new context, so a child with z-index: 1000 can still sit under a neighbor of its parent. isolation: isolate creates a context on purpose, which keeps a component's layers from leaking out.
6. Overflow, min and max sizes, clipping
Content does not always fit the box it is given. A long word, a wide table, a reader's 200 percent text size, a German translation: each can push content past the edge. CSS lets content overflow visibly by default, on the principle that it is better to see content spill than to lose it.
overflow
| Value | Effect |
|---|---|
visible (default) | Content spills out and may overlap whatever follows |
auto | Scrollbars appear only when the content overflows |
scroll | Scrollbars are always present, needed or not |
hidden | The overflow is cut off, but the box is still a scroll container that scripts and focus can scroll |
clip | The overflow is cut off and the box is not a scroll container at all |
Prefer auto when content might not fit, for example on a wide data table inside a narrow column. hidden is the one to be careful with: content the reader cannot reach is content lost, and it often appears only when someone zooms. An ancestor with overflow: hidden also becomes the scroll container that a position: sticky descendant sticks within, which is the usual reason a sticky header mysteriously stops sticking. Scrolling regions also print badly, since paper does not scroll, so a print style sheet should let them expand. For text, overflow-wrap: anywhere lets a long URL or identifier break rather than overflow.
Minimum and maximum sizes
min-inline-size and max-inline-size (and their block equivalents) set the limits a box may shrink or grow to as its container changes. They are underused and among the most useful properties for adaptive design: a column that grows with the window but stops at a readable measure, an image that never overflows its container. When a minimum and a maximum conflict, the minimum wins.
clamp(MIN, PREFERRED, MAX) folds the three into one value; the Values page covers it with min() and max().
Clipping
The original clip property cut an absolutely positioned box down to a rectangle written as rect(top, right, bottom, left). It is deprecated, and it only ever worked on absolutely positioned boxes:
Its replacement, clip-path, works on any element and takes shapes: inset() for rectangles, plus circle(), ellipse() and polygon(). The basic shapes are widely available.
7. Flexbox
Flexbox arranges items along one axis. A container with display: flex turns each of its direct children into a flex item. The main axis is the direction the items run, set by flex-direction; the cross axis is perpendicular to it. Flexbox properties talk about main and cross rather than horizontal and vertical, because flex-direction: column swaps them.
flex-direction; justify-content works along it and align-items across it.flex-wrap: wrap lets items move to a new line when the main axis runs out of room, and gap sets the space between items and between lines. gap replaced the old habit of putting margins on every child and a negative margin on the container to cancel the outer ones.
How flex items share space
Each item has three sizing inputs, usually set together with the flex shorthand:
| Property | Meaning | Default |
|---|---|---|
flex-basis | The item's starting size along the main axis | auto: its content size |
flex-grow | Its share of leftover space | 0: does not grow |
flex-shrink | Its willingness to give up space when there is too little | 1: shrinks in proportion |
The algorithm, in outline: size each item at its basis, add them up with the gaps, and compare with the container. If space is left over, hand it out in proportion to flex-grow. If there is too little, take it back in proportion to flex-shrink, weighted by each item's size. Then clamp each item to its minimum and maximum.
flex: 1means1 1 0%: ignore content size and share the space equally.flex: automeans1 1 auto: start from the content size and share the extra.flex: nonemeans0 0 auto: keep the content size, no matter what.flex: 0 0 10remmeans a fixed 10rem column that neither grows nor shrinks.
Alignment
| Property | Set on | Aligns |
|---|---|---|
justify-content | Container | Items along the main axis, sharing out free space: start, center, end, space-between, space-around, space-evenly |
align-items | Container | Items along the cross axis: stretch (default), start, center, end, baseline |
align-self | Item | One item along the cross axis, overriding align-items |
align-content | Container | Wrapped lines along the cross axis |
Older code uses flex-start and flex-end; the plain start and end mean the same in a normal flex container and are shared with grid. Together, two alignment properties solve the problem that defeated CSS for most of its history, centering a box both ways:
The order property can rearrange flex items visually without touching the HTML. Use it rarely. Keyboard focus and screen readers follow the source order, so a page whose visual order disagrees with its DOM order sends sighted keyboard users jumping around the screen. If the order matters, change the HTML.
8. Grid and subgrid
Grid arranges items in two dimensions at once. Unlike flexbox, it has no favored axis: the container defines rows and columns of tracks, and items are placed in the cells where tracks cross, spanning as many as they need. display: grid makes a grid container, and grid-template-columns and grid-template-rows define its tracks.
Tracks and the fr unit
| Track size | Meaning |
|---|---|
10rem, 30% | A fixed or percentage size |
auto | Sized by the content in the track |
min-content, max-content | As narrow as the content allows, or as wide as it wants without wrapping |
1fr | One share of the space left after the other tracks are sized |
minmax(a, b) | No smaller than a, no larger than b |
repeat(3, 1fr) | The same track, three times |
repeat(auto-fit, …) | As many tracks as fit, with empty ones collapsed |
One line of grid makes a responsive card layout with no media query: as many columns of at least 15rem as fit, sharing the leftover space. The inner min() keeps a single column from overflowing a container narrower than 15rem.
Behind that line is a real solver: an item spanning several tracks constrains their sum, so the browser sizes fixed and content tracks first, grows them to fit the items, then hands the remaining space to the fr tracks.
Lines and areas
Grid lines are numbered from 1 at the start edge, and negative numbers count back from the end, so -1 is always the last line. An item can be placed between lines, with span for an extent. More readable still, grid-template-areas draws the layout as a map: each string is a row, each word a cell, and repeating a name across cells makes one area span them. A period leaves a cell empty.
grid-template-areas.The implicit grid
When there are more items than explicit cells, the grid adds rows (or columns, with grid-auto-flow: column) on its own. grid-auto-rows sizes those extra tracks, so a feed of unknown length still lines up: grid-auto-rows: minmax(10rem, auto).
Alignment in two dimensions
Grid uses the flexbox alignment vocabulary, with fixed axes: justify-* always works in the inline direction and align-* in the block direction. justify-items and align-items place items within their cells; justify-content and align-content place the whole grid within its container when it is smaller. The place-items shorthand sets both, so display: grid; place-items: center is the shortest centering in CSS.
Subgrid
A row of cards, each with a heading, a paragraph and a link, looks ragged when one heading wraps onto two lines and its card's paragraph starts lower than the others. Each card is its own box, so nothing ties the parts of one card to the parts of the next. Subgrid lets a grid item adopt its parent's tracks instead of defining its own, so every card's heading sits in the same row track. Subgrid is widely available.
Each li spans three of the parent's rows and lays its own three children (heading, paragraph, link) into them. A tall heading in one card now grows the shared row, and every card follows.
Multi-column
One more layout model flows text rather than placing items. column-width: 20rem splits an element's content into as many columns of about 20rem as fit, reading down one and on to the next, like a newspaper. It suits short lists better than long articles, where readers would scroll back up for each column.
9. The holy grail twice: flex, then grid
The "holy grail" is the classic page: a header across the top, three columns (navigation, main content, a sidebar) in the middle, a footer across the bottom, the middle row filling the screen even when the content is short. With floats it was famously hard. With flexbox and grid it is short, and building it both ways shows what each algorithm is for.
With flexbox: two containers
Flexbox works in one dimension, so the layout needs two flex containers: the body as a column (header, middle, footer), and a row inside it for the three columns. That row needs an element to be its container, so nav and aside have to go inside main.
The outer column's main grows with flex: 1 and pushes the footer down. Inside it, the side columns hold their basis and the article takes the rest. The side columns run the full height of the row because the default align-items is stretch, which floats could never do.
With grid: one container
Grid works in two dimensions, so one container does it all, and the CSS reads like a sketch of the page:
Comparing the two
| Flexbox version | Grid version | |
|---|---|---|
| Containers | Two, nested | One |
| Markup | nav and aside moved inside main to form the row | All five regions are siblings in body, as their meaning suggests |
| Sizing | Each item claims space with flex | The container declares the tracks |
| Rearranging | Change directions on both containers | Rewrite one map |
The markup row matters most. In the flex version, layout dictated the HTML: the site navigation sits inside main only because flexbox needed a row container. Grid lets the HTML say what each region is and lets the CSS place it. That is the house style's first rule, structure first, made possible by the layout system.
A good rule of thumb: flexbox when content decides the size (a nav bar whose links are as wide as their text), grid when the container decides it (a page or a card grid with planned tracks). Real pages use both, and normal flow inside both.
Choosing an algorithm
| The problem | Reach for |
|---|---|
| Prose, headings, lists and figures in reading order | Normal flow, with a measure and some spacing |
| Text wrapping around a figure | A float |
| A row or column of items sharing space | Flexbox |
| Page regions, card grids, aligned forms | Grid |
| Lining up the insides of sibling components | Subgrid |
| Badges, menus, sticky headers | Positioning |
| A short list flowed into newspaper columns | Multi-column |
The mistake to avoid is reaching for grid or flexbox where normal flow already works. Most of a page is a document, and flow handles documents with less code than anything else.
All nine demos
Each demo is a standalone page sharing two small style sheets, layout-vars.css (tokens) and layout-global.css (reset and type, in layers). Every layout rule lives in the demo's own style element, explained in a comment in its head.
- Normal flow: block, inline, and a centered column
- Float: text wrapping a figure, and
clear - Relative position: nudged, with its space kept
- Absolute position: pinned inside a relative box
- Flexbox centering
- Flexbox page: header, main and footer
- Flexbox holy grail
- Grid page: header, main and footer as named areas
- Grid holy grail, remapped by one media query
Media queries and container queries, which make layouts like these respond to their context, are the subject of the next page.
10. Summary
| Concept | Key points |
|---|---|
| Layout as an algorithm | The parent's display picks the algorithm: normal flow, flexbox, grid or subgrid. Each solves the same question with different constraints, in logical terms. |
| The box model | Content, padding, border, margin. Size with inline-size and block-size, and set box-sizing: border-box in the reset so the size you set is the size you see. |
| Logical spacing | Use margin-inline, padding-block, border-inline-start and inset, so layouts survive translation. Block margins collapse in flow; gap avoids the issue. |
display | An outer type (block or inline) and an inner type (flow, flex, grid). flow-root contains floats; contents needs accessibility testing; changing display never changes meaning. |
| Flow, floats, positioning | Flow is the default and usually right. Floats are for wrapping text, not page layout. Position to take something out of the flow, relative to a positioned ancestor; z-index works within a stacking context. |
| Overflow and limits | Prefer overflow: auto to hiding content. Use min- and max- sizes or clamp() instead of fixed sizes. clip-path replaces clip. |
| Flexbox | One axis. flex sets basis, grow and shrink; justify-content aligns on the main axis, align-items on the cross. Add min-inline-size: 0 when an item refuses to shrink. Avoid order that fights the source. |
| Grid and subgrid | Two dimensions of tracks. fr, minmax() and repeat(auto-fit, …) make responsive grids; lines and named areas place items; subgrid aligns the insides of cards. |
| The holy grail | Flexbox needs two nested containers and bends the markup; grid uses one container and a map that one media query can redraw. Use each where it fits, with normal flow inside both. |
