Pretext + flexbox
A layout is more than one paragraph. A real UI is boxes inside boxes — a header beside a sidebar beside a body, each with text at the leaves. Pretext tells you how tall a paragraph is at a given width. Pair it with a tiny flex solver and you can compute the whole tree's geometry — every box's x, y, width, and height — entirely in JavaScript, before anything mounts.
Drag the width slider. Five nested boxes re-arrange and re-wrap in lockstep: Pretext measures each text leaf; the solver distributes space along the main axis and stacks the column along the cross axis. No DOM reflow is ever triggered — these are SVG-like rectangles whose positions come from arithmetic.
Mechanism
The tree is a plain JS object: a row container wrapping three children — a text leaf on the left, a column of three text leaves in the middle, and a text leaf on the right. Each text leaf carries a string; each container carries a direction (row / column) and a list of children with flex weights.
At boot we call prepareWithSegments(text, font) once per text leaf — the handles are reusable across every width the slider will ever produce. On each slider event we run a pure-JS solver:
- Row container: split its inner width by child
flexweights. Each child gets a main-axis width. - Text leaf: call
layout(handle, childWidth, lineHeight)— Pretext returns{ height }. That's the cross-axis size. - Column container: recurse top to bottom. Each child gets the column's width; its returned height stacks below the previous one.
The solver is one recursive pass. For this tree it runs in microseconds. We then stamp absolutely-positioned <div>s at the computed coordinates — one assignment per node, no getBoundingClientRect, no offsetHeight.
Application
Once layout is arithmetic, a whole category of things becomes easy:
- DOM-free 2D layout. Compute positions for a React tree server-side or in a worker, then hydrate once. No layout thrash on mount.
- AI coding agents with "layout vision." An agent editing a UI can ask "does this button's label overflow at 320px?" without spawning a real browser — Pretext + a flex solver answer from a plain Node process.
- Offscreen measurement before hydration. Virtualized lists, animated reflows, "what if" width probes — all while the real DOM holds still.
- Speculative resize. Test a dozen candidate widths per frame and pick the one whose tree fits some aesthetic rule (line-count parity, golden-ratio heights, no orphans).
Inspired by Textura (Pretext × Yoga) by Charlie Greenman. This demo implements a tiny subset of the pattern — Textura is the full library, backed by Facebook's Yoga engine.
"A sentence, well set, ends by giving back what it was given. A room, well arranged, does the same. Each part is held by its neighbors, and the whole is held by its parts — form containing form, and no part anywhere that does not know its place."
Contemplative registerDirect Claude
layout() is cheap — try multiple widths, pick the winner
import { prepareWithSegments, layout } from '@chenglou/pretext';
// Tree: each node is either a text leaf (handle + flex) or
// a container (direction + children with flex weights).
const tree = {
kind: 'container', direction: 'row', flex: 1,
children: [
{ kind: 'text', flex: 2, handle: prepareWithSegments(T1, FONT) },
{ kind: 'container', direction: 'column', flex: 3, children: [
{ kind: 'text', flex: 1, handle: prepareWithSegments(T2, FONT) },
{ kind: 'text', flex: 1, handle: prepareWithSegments(T3, FONT) },
{ kind: 'text', flex: 1, handle: prepareWithSegments(T4, FONT) },
]},
{ kind: 'text', flex: 2, handle: prepareWithSegments(T5, FONT) },
],
};
// Solve: recurse the tree, writing x/y/w/h onto each node.
function solve(node, x, y, w, h) {
node.x = x; node.y = y; node.w = w; node.h = h;
if (node.kind === 'text') return;
const total = node.children.reduce((s, c) => s + c.flex, 0);
if (node.direction === 'row') {
let cx = x;
for (const c of node.children) {
const cw = (c.flex / total) * w;
solve(c, cx, y, cw, h);
cx += cw;
}
} else {
let cy = y;
for (const c of node.children) {
const ch = (c.flex / total) * h;
solve(c, x, cy, w, ch);
cy += ch;
}
}
}
// Text-leaf height comes from Pretext, not the DOM.
function measureLeafHeight(node, lineHeight) {
return layout(node.handle, node.w, lineHeight).height;
}