Skip to content
codingncaffeine edited this page Sep 20, 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 where the Unicode line breaking algorithm allows (the bullet after next), whitespace collapsing per white-space (normal, nowrap, pre, pre-wrap, pre-line, break-spaces), 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. A box sized to its content makes room for it: the indent lengthens the first line of a max-content width, and the first piece of a min-content one.
  • Where a line may end is what UAX #14 says of the paragraph's text — Unicode 16, every rule, scored on the conformance file (see Testing): after a space or a hyphen, between ideographs, after the slash of a URL, never inside a number or in front of a closing bracket — and a word is cut into the pieces between its opportunities before it is laid out. The edge of an inline box is no such place, so un<b>believable</b> moves to the next line whole when its second half does not fit, the line backing up to where it last could have ended. A zero width space is an opportunity, a word joiner takes one away, and neither is drawn. So is a soft hyphen, and a line that ends at one ends with a hyphen — hyphenate-character, else U+2010 where the font has it and a hyphen-minus where it does not — that has to fit, or the line ends at the place before it; hyphens: none takes the opportunity away, and word-break: auto-phrase holds it back until a word cannot fit a whole line without it. CSS tailors the rules: word-break (break-all, keep-all, break-word, manual), line-break (strict, normal, loose, anywhere; the language-conditioned part of loose is not written) and overflow-wrap (normal lets a word too wide for a line overflow, as browsers do; break-word and anywhere slice it at the last glyph that fits, and only anywhere counts the slices toward a min-content width). A preserved space at a line's end hangs past it under pre-wrap — counted for neither the fit nor the alignment, except before a forced break, where it counts when it fits — and so does an ideographic or other space separator under every value but break-spaces, where spaces take their room and a line may end after any of them. Thai, Lao, Khmer and Myanmar, whose words a dictionary finds, break between clusters for want of one.
  • 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 root's margins collapse with nothing: its top margin is room above the page's first box.
  • 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. Down a vertical page a picture has no edge to stand on, so it is set astride the line by its middle instead, and a line holding one picture measures the picture and nothing more.
  • 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 a cut is no place to end a line — 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; one that does spells the controls its unicode-bidi names (isolate-override an isolate with an override inside, plaintext an isolate that reads its first strong character), and an override on a block holds all of its inline content to the block's direction. The formatting characters written in the text itself — the embeddings, overrides and isolates with their pops, and the marks — are read the same way and drawn as nothing, and a preserved tab reaches the algorithm as the segment separator it is. An inline box's margin, border and padding stand at the ends of its content in visual order, the side it opens on being the one its own direction starts from (CSS 2.1 §8.6). HTML's user-agent rules make an element with a dir attribute an isolate, <bdo> an isolate-override, and <bdi> an isolate that takes the direction of its first strong character unless it states one.
  • 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 — an absolutely positioned child between two runs of text parts them into two, in a grid as here — 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; the items of a row that ask for baseline share one, each measured at the width it is about to have and the rest pushed down to meet the furthest, with the shift counted towards the row's own size so an automatic row comes out as tall as that baseline plus the deepest descent below one; justify-self: baseline down a column 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.
  • position: sticky keeps a box inside what its offsets leave of the scrollport it is in, without letting it leave the block that contains it. The scrollport is the padding box of the nearest ancestor that scrolls or clips, or the viewport where there is none; it is settled in a pass over the finished tree, so what is inside a box that moves travels with it — an inline one included, since its words now travel with its box (Painting and text). What this shows is what a reader sees on opening the page: a box whose offset holds it clear of an edge it already sits past is moved to meet that edge, as far as its containing block allows and no further, and an offset already satisfied moves nothing. It is settled once, when the page is laid out, so a box does not re-stick while a scrollport is being moved.
  • A position: fixed box is placed against the viewport, and the viewport alone clips it and says where it can be pressed — but it is painted where it stands in the tree: its box is kept under the nearest ancestor that forms a stacking context, so a dialog written inside its backdrop is drawn, and pressed, over that backdrop. Fixed boxes first met while another is being placed (a page whose body is fixed to hold it still under a dialog, the dialog fixed inside a fixed backdrop — the way a consent dialog is written) are placed in turn until none is left.
  • 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. A box that was written as inline-level begins where its line had reached, after text-align has placed that line in the room the floats leave; a block-level one spans the block's content box and takes neither the first line's indent nor a float's room. justify-self and align-self place a box given a grid area inside that area, with safe handing the start edge back to one that does not fit. 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 below one paints the box and everything in it as one group at that alpha, and opacity: 0 hides it outright (Painting and text). overflow other than visible clips what a box contains to its padding box, positioned descendants included when the box is their containing block; auto, scroll and hidden also make the box a scroll container, and layout settles how far its content can be moved to reach what the clip hides (The shell). clip clips and nothing more.

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 is as wide as it was made and overflows, as in every browser — max-width is what holds one to its column, and a sprite sheet at width: 300% inside a box that clips is meant to be three boxes wide. The attributes reach layout through the cascade, as the presentational hints they are, and nowhere else: height: auto on a picture as wide as its column is the author's word and wins over height="1080" — the way nearly every page sizes its pictures — while until the picture has arrived the two attributes give the box the shape it will have, so the page does not jump. A canvas is as large as its attributes say. srcset, sizes and <picture> choose the source for the viewport (see Painting and text).
  • aspect-ratio (auto, a ratio, or both): a replaced box with one side given takes the other from the ratio — the style's, unless it says auto as well and the content has a ratio of its own — and an ordinary block with no height of its own takes it from its width, of the content boxes or, under border-box, of the border boxes. That height is a definite one, so a child's height: 100% is a percentage of it; while the box lets its content show past it, what is inside can still make it taller (the automatic minimum).
  • object-fit (fill, contain, cover, none, scale-down) and object-position lay an <img>'s picture into its content box: at the shape it keeps, placed by the position — a percentage lining that point of the picture up with the same point of the box — and shown through the box, the rest cut off.
  • 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, except an object showing its fallback content, which is an ordinary element with its children laid out (Scripting has how an object or embed decides what it shows). An embed that shows nothing, or has not decided yet, has no size of its own, so only a size written for it holds, as Chrome and Firefox measure it; an object or embed showing a picture is drawn as an img is, and one showing a document draws it as an iframe does. A page laid out without scripts has made no decisions, and its objects and embeds stay the boxes described first. An iframe wears the 2px inset border HTML's rendering section gives it (frameborder="0", or a frameborder that is no number, takes it away) and shows its document inside: laid out and painted at the size of its content box, which is that document's viewport — 100vh is the frame's height — on a canvas left transparent unless the document colours it, so the frame's own background shows through. A frame's own frames are drawn the same way; Security defaults has the rules a frame is held to, and Not written yet what a frame does not do.
  • An inline <svg> is a replaced box drawn from its own content (Painting and text): sized by CSS, else by its width/height attributes, else by its viewBox's ratio — with only a ratio it takes the width it has and follows the ratio down, which is what a responsive picture wants — else 300 by 150. A viewBox with a zero side draws nothing, as the SVG specification says.
  • 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, 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, a written percentage width taken of the containing block. A control with a line of text in it — a field, a button, a select — stands on its line by that text's baseline, as an inline-block does by its last line's, so a label and its field read along one line; a checkbox, a radio button and a text area stand by their bottom edge. The author's padding is part of the control's box and what its text stands in from: a button or a select is sized border-box as browsers' own sheets have it, a text field content-box, so a written width is the box or the box without the padding accordingly. An empty field shows its placeholder in its own color at a little over half strength; a field not being typed into shows its value from the start, one that is keeps its caret in view; and the text is cut to the width the field ends up with, a flex line's included. 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