Skip to content
codingncaffeine edited this page Sep 4, 2026 · 34 revisions

Layout

Layout is a pure function of the styled tree and the viewport: styles in, an immutable fragment tree out, in page coordinates. Painting consumes fragments only.

Block and inline

  • Block containers stack their block children; inline content is broken into line boxes with greedy breaking, whitespace collapsing per white-space (normal, nowrap, pre, pre-wrap, pre-line), an emergency break inside a word that cannot fit a line, and text-align — justify shares the room a line did not use out among the spaces between its words, and start and end become sides once the block's direction says which end its content runs from (or, under dir="auto" and unicode-bidi: plaintext, once the text's own first strongly directional character does), leaving the last line of the block, and any line ended by a forced break, wherever text-align-last puts it (by default the start edge, and justify-all is how a block asks for its last line to be stretched with the rest). text-justify: none turns justification off.
  • text-indent narrows the first line by the amount it is given, taken off the end that line starts at: the left in a left-to-right block, the right in a right-to-left one. Under dir="auto" or unicode-bidi: plaintext it is the paragraph's own direction that says which.
  • Margins collapse per CSS 2.1 §8.3.1: between siblings, through a parent's open top and bottom edges, and through empty boxes; a cleared box keeps its margin inside, and an empty box holding a float ends the walk.
  • The room a width leaves over goes to the margins (§10.3.3): both auto share it, which centres the box; one auto takes all of it, which is how a box is pushed to the far end. With neither auto the equation is over-constrained and a margin is ignored and solved for instead — the one at the end the content runs towards, so a box narrower than its containing block settles against the right edge when that containing block reads right to left, and one too wide overflows the left. It is the containing block's direction that decides this, not the box's own. There is no room to give away when the box is already the wider of the two, so an auto margin is zero there rather than pulling the box off the start edge.
  • Percentage widths resolve against the containing block; percentage heights (and min-height, max-height) against a definite containing height — a written length, a percentage of a definite height in turn, a flex line's settled size, an absolutely positioned box's containing block, or the viewport for the root — and stay auto without one.
  • min-width, max-width, min-height and max-height hold blocks, floats, flex items and replaced boxes.
  • box-sizing: content-box (the initial value) and border-box, under which a written width, height, min-/max- size or flex basis names the border box — the padding and the borders come off it, and the content floors at zero when they are wider than the size written. A form control is sized whole either way: its own border and padding live inside the size it reports.
  • Lists number themselves with the list-item counter (css-lists-3): every display: list-item box adds one to it, <ol> and <ul> reset it, and start, value and type are read as the counter and list-style properties they stand for, so an author’s own counter-reset: list-item on a list changes the numbering. Markers: the glyphs (disc, circle, square), the numbering systems (decimal, decimal-leading-zero, lower and upper roman, alphabetic under both the -alpha and -latin names, lower greek, armenian, georgian) and none. list-style-position: outside hangs the marker to the left of the item’s box, where a list with its padding zeroed can lose it off the edge of the page — as browsers do; inside makes it the first thing on the item’s first line, so an item holding nothing else still shows its marker and makes a line for it.

Inline boxes and the line

  • An inline box draws its own background and border and takes room for its margin, padding and border where it opens and where it closes. Broken over several lines it is one box sliced: the line it carries on to draws neither the edge it never reached nor the one it has not come to. Broken around a block it is two complete boxes, each with all four of its edges.
  • A block-level box inside an inline box splits the inline box around it and lays out as a block child of the containing block (CSS 2.1 §9.2.1.1): a card inside a link keeps its border, padding and background.
  • The line box follows CSS 2.1 §10.8: every box on the line reaches above and below the baseline it is aligned to — the strut of the block's font, text by its ascent and descent within its line height, a picture by its height, an inline-block by its own ascent and descent — and the line runs from the uppermost top to the lowermost bottom.
  • vertical-align on inline boxes and atomic boxes alike: baseline, sub, super, text-top, text-bottom, middle, top, bottom, a length, or a percentage of the box's own line height. A run inside nested spans takes the alignment of the nearest raised or lowered inline box around it; top and bottom boxes sit at the line's edges and grow the line when taller.
  • line-height: normal is what the face asks for (its ascent, descent and line gap); a written line height places the baseline by the half-leading model.

Inline-block

display: inline-block, inline-flex and inline-grid (and inline-table until tables land) lay out as one atomic box on the line: shrink-to-fit wide like a float, the margin box what the line holds, sitting on the baseline of its last line box — its bottom margin edge when it has none, and the higher of the two when it clips its overflow, as CSS 2.2 has it. An absolutely positioned inline-block becomes a block with an inline static position.

Floats

  • float: left and float: right, placed at their point in flow — mid-line too — no higher than an earlier float, below what their own clear names, then down until they fit beside the floats at that height.
  • clear on blocks and on <br> (the CSS property or the clear attribute).
  • Auto widths shrink to fit from the min-content and max-content widths of the float's contents.
  • Bidirectional text — the items of a line carry the embedding level the Unicode bidirectional algorithm gave them (see Testing for how that is scored), a word whose characters do not all share a level is cut where it changes, and rule L2 settles the order the entries are drawn in. A run that reads right to left has its characters reversed and its brackets mirrored (L4), so a parenthesized aside still reads as one. An inline box that opens no level of its own is invisible to the algorithm and belongs to the run beside it.
  • A box can ask for those sizes by name: width, min-width, max-width and flex-basis read min-content (the widest piece that cannot break), max-content (everything on one line) and fit-content (the narrowest held up to the room, never past the widest — the same formula shrink-to-fit uses). A block sized that way no longer fills its container, so auto margins centre it, and two offsets no longer stretch an absolutely positioned one.
  • Boxes with their own formatting context (overflow other than visible, display: flow-root, flex containers, inline-blocks, floats themselves) contain their floats and sit beside outside floats — narrowed, or below them when a written width does not fit.
  • Line boxes and formatting-context boxes keep clear of floats along their whole height (CSS 2.1 §9.5), not only at their top: a float starting lower narrows the line, or moves it below the next float when its content no longer fits.

Flexbox

display: flex and inline-flex: the items (element children in order, the container's own text as anonymous items, order sorted), each item's flex base size and automatic minimum, lines (flex-wrap, both directions), flexible lengths (grow into free space or shrink out of overflow, weighted, never below the minimum, with items frozen at their minimum and the rest re-shared), cross sizes, align-items/align-self (stretch, flex-start, flex-end, center; baseline as flex-start for now), align-content across wrapped lines, justify-content (all six values, the reverse directions swapping the ends), row-gap/column-gap/gap, flex-direction in all four directions, and the flex and flex-flow shorthands. A single-line container gives its line the container's cross size; a wrapping container whose items fit one line keeps that line its items' size.

Either axis can run backwards. A row's main axis is the inline axis, so direction: rtl reverses it and the first item is the rightmost — and row-reverse on top of that cancels it. A column's main axis runs down whichever way the text reads, and it is that column's cross axis that is the inline one, so a right-to-left column lays its items out from the right edge. flex-wrap: wrap-reverse swaps the cross ends on top of either, which is what turns align-items: flex-start into the far side and packs the lines against the other edge.

An auto margin on an item takes the room its line has left over — shared equally among however many say auto, before justify-content is consulted at all, which is what puts one item at the far end of a bar and leaves the rest where they are. Across the line an auto margin takes the leftover the same way and the item's own alignment is not consulted. flex-basis: content sizes from the content rather than falling back to the width property. Baseline alignment is not written.

Grid

display: grid and inline-grid, css-grid-1 without subgrid and masonry:

  • The explicit grid — grid-template-columns and grid-template-rows track lists: lengths and percentages, fr, auto, min-content, max-content, minmax(), fit-content(), repeat() with a count or auto-fill/auto-fit (as many repetitions as fit; auto-fit collapses the empty ones), and line names in brackets; grid-template-areas (each area a rectangle, or the declaration is dropped) with the -start/-end line names it implies; the grid-template and grid shorthands, grid-auto-flow included.

  • Placement — grid-row, grid-column, grid-area and their longhands: line numbers (negative from the end), the nth line with a name, a name alone (its area's edge, else the first so named), span by count or to a named line; lines outside the explicit grid make implicit tracks, sized by grid-auto-rows/grid-auto-columns (the pattern runs forward after the grid and backward before it). Auto-placement (css-grid-1 §8.5) in row or column flow, sparse or dense, in order-modified document order; the container's own text becomes anonymous items, its ::before/::after boxes items placed by their own properties.

  • Track sizing — the algorithm of css-grid-1 §11: base sizes and growth limits from the items' minimum, min-content and max-content contributions (single-track items first, then spanning items by span, then items crossing flexible tracks), the free space maximizing the tracks, fr shares found by the fr-size search, and auto tracks stretched by justify-content/align-content: normal or stretch. Percentage tracks and gaps resolve against the container's content box; under an indefinite height they count as auto while the content height is found, then resolve against it and the rows are sized again.

  • Alignment — justify-content and align-content distribute the free space between the tracks (start, end, center, space-between, space-around, space-evenly, stretch); justify-items/justify-self and align-items/align-self place an item in its area (stretch, start, end, center; normal stretches a block and keeps a picture or control at its own size; baseline is start for now); auto margins take the free space first; place-items, place-self and place-content. A grid item's percentages resolve against its area. z-index on a grid (or flex) item orders it among its container's positioned boxes.

  • Columns that run from the right — a grid whose direction is rtl reads its column axis from the content box's other end, so column one is the rightmost and the room justify-content leaves over is at the left. It is done by mirroring the areas once they are settled, which leaves track sizing, placement and the free-space share-out exactly as they were; where an item sits inside its area is settled in physical offsets, so justify-self: start is separately swapped to mean the area's right edge. An absolutely positioned child placed by grid lines gets the mirrored area as its containing block.

  • Sizing the container — an inline-grid, a float or a flex item that holds a grid shrinks to fit: the columns sized under a min-content and a max-content constraint, gaps included.

  • Absolute positioning (css-grid-2 §9) — an absolutely positioned child of a grid container is not a grid item, but when the container is also its containing block its placement still names a grid area, and that area is the containing block: an offset counts from the area's edge, a box with auto offsets stands at the area's corner and shrinks to fit, and a box stretched between opposite offsets fills the area. A property naming a line the grid does not have counts as auto, and an auto edge stays on the container's padding edge, so one axis can be the area's and the other the padding box's.

Baseline alignment, subgrid and grid-lanes are not written.

Writing modes

Layout reads a box's inline axis as its x and its block axis as its y, which is what a horizontal writing mode means. A vertical one — vertical-rl, vertical-lr, sideways-rl, sideways-lr — is laid out through styles whose physical edges have been permuted into that frame: the inline-start edge answers to left, the block-start edge to top, and a height stands where a width would. Every algorithm above then runs unchanged, and the fragments are turned back into page coordinates once the frame is finished. The properties already written in flow-relative terms — the flex and grid axes, the gutters — and the line-relative ones — float, clear, text-align, vertical-align — are in the frame as they stand and are left alone.

The lines stack from the viewport's right edge in the -rl modes, which is where a reader of one of them starts, and a page too wide for the viewport runs off to the left, as it does in a browser opened at the top of the document. The document's mode is the root's, taken from its body child where there is one.

A box whose mode is not the one around it starts an orthogonal flow. Its own margins, borders and size belong to the formatting context it sits in; only what is inside it turns. The room its lines have is its own extent along the outer block axis, and where that is not written the specification names a fallback rather than leaving it circular — the initial containing block's size in the box's inline axis, which the content then shrinks to fit within. What the box meets the outer flow with is its own block size, and an auto block size is the size of the content, never the room its container has.

Text turns with the frame: every glyph a quarter circle, clockwise in the modes that read downwards and the other way in sideways-lr. vertical-lr is the one mode whose ascenders face its block end rather than its start, so a line laid out in that frame is turned over inside its own box, which is what lands the turned ink back where the line reserved room for it.

Positioning

  • position: relative shifts a box after layout without touching the flow — a block, a float, a flex or grid item, an inline box, or an inline replaced box such as a picture, each taking its own content with it while the content after it stays where the flow put it. sticky stays put until scroll containers land.
  • position: absolute and fixed: the box is placed in the nearest positioned ancestor's padding box (or the viewport) by its offsets — width as written, stretched between left and right, else shrink-to-fit in what the offsets leave; height likewise; over-constrained boxes solve their auto margins; a box with no offsets stands at its static position, where it would have been in flow. Replaced boxes keep their own size; the offsets place them without stretching.
  • Painting follows CSS 2.1 Appendix E: a positioned box with a z-index is a stacking context painted as one unit at its level; negative levels go under the context's own flow.

Hiding

visibility: hidden keeps a box's room and paints nothing (a visible descendant paints). opacity: 0 hides a box and everything in it; between zero and one the box paints as if opaque for now. overflow other than visible clips what a box contains to its padding box, positioned descendants included when the box is their containing block.

Transforms

The translations of transform (translate, translateX, translateY, translate3d, added up along a list) and of the translate property move a box after layout, percentages of its own size; any transform makes the box a stacking context. Rotations, scales and skews are passed over, leaving the box where it is.

Replaced boxes

  • Pictures are sized by CSS, by their width/height attributes, or by themselves, the ratio kept; a picture wider than its container shrinks to fit. srcset, sizes and <picture> choose the source for the viewport (see Painting and text).
  • An iframe, canvas, video, embed or object is a box of its own too — 300 by 150 when nothing sizes it, as CSS 2.1 §10.3.2 has it — and none of them show their children.
  • On a line, a replaced box carries its margins, border and padding; the picture sits inside them and the bottom margin edge rests on the baseline.

Form controls

Text fields, password fields, textareas, checkboxes, radio buttons, selects and buttons lay out as atomic boxes on the baseline, sized by their kind (size, cols/rows, the caption, the widest option) or by CSS, with their margins around them; block-level controls stand on their own. See The shell for how they behave.

Tables

CSS 2.1 §17, the separated border model:

  • Structure — every display value of the table model: table, inline-table, the row groups (thead first and tfoot last, wherever they stand), rows, cells with colspan and rowspan (a rowspan of 0 reaches the end of its group), columns and column groups with span, captions (caption-side: top or bottom). The anonymous boxes of §17.2.1 are generated: a run of table parts outside a table gets an anonymous table around it (an inline-table inside inline content), loose content in a table or a row group gets an anonymous row, and loose content in a row an anonymous cell, blank text between parts dropped.
  • Widths — the automatic layout of §17.5.2.2: each column's minimum and maximum from its cells' content and written widths, spanning cells widening the columns they span in proportion, percentage columns taking their share first, written widths holding, the other columns sharing the rest — their maximums when it fits, a point between their minimums and maximums when not. A table's width is what it is written to be (an HTML table's border box; another element's content box), else it shrinks to fit the room; table-layout: fixed takes the widths of the columns and of the first row's cells and shares the rest equally. border-spacing and the table's border and padding count in.
  • Heights — a row is as tall as its tallest cell, the baseline-aligned ones lined up first; a spanning cell that needs more gets it from the last row it spans; a written row or cell height is a floor; a written table height stretches the rows in proportion. Cells sit in their rows by vertical-align (top, middle — the row groups' default — bottom, baseline).
  • Column order — column one of a right-to-left table is the rightmost (§17.2), and it is the table's own direction that says so, not the direction of whatever contains it. The widths, the spacing and the spans are settled in column order and the run is then mirrored, so a spanning cell keeps its columns together and a column's background follows it across.
  • Painting — the table's border and background, the column groups' and columns' backgrounds under the rows, the row groups' and rows' under the cells, and each cell's own border and background; the table's baseline is its first row's.
  • Presentational attributes — the HTML rendering section's mappings arrive as author declarations of zero specificity: width, height, bgcolor, align, valign, nowrap on the table parts (and align/bgcolor/text on body, div, p, headings, hr, img with hspace/vspace/border, font with color/size/face), and a table's cellspacing, cellpadding and border, the last two reaching its cells.

The collapsing border model (border-collapse: collapse, CSS 2.1 §17.6.2) lays out with no gutters and no table padding, and the borders merge. At every segment of every grid line the table, its column groups, columns, row groups, rows and cells all offer a border, and §17.6.2.1 picks one: a hidden border takes the line away outright, none loses to everything, otherwise the widest wins and equal widths go to the box that comes first in the list cell, row, row group, column, column group, table. The winner runs down the middle of the line and the boxes on either side each keep half of it, so the shared line is drawn once at its full width; a box that spans takes the widest segment along each of its edges, and the table box keeps the outer half of its outermost lines. empty-cells: hide and visibility: collapse on rows and columns are not written.

Reading the layout

sashfold --render page.html -o out.png --dump-layout

prints every fragment — tag, id and class, position, size, baseline, and flags for floats, positioned boxes and stacking contexts — and every text run with its position, baseline and width.

Clone this wiki locally