Pretext: The Pure-JavaScript Typography Engine That Skips the DOM Entirely

For most of the web's history, "measuring text" has meant one thing: stick a <span> in the document, read its getBoundingClientRect(), and let the browser do the layout work. It works — but it forces a layout reflow every time you ask, and that reflow is the single most expensive operation in client-side rendering. Pretext, created by Cheng Lou (React core alumnus, react-motion author, ReScript and Midjourney contributor), takes a completely different path: it computes text layout as pure arithmetic, without ever touching the DOM.

The result is a typography engine you can run on every animation frame.

The Core Idea: Layout as Math, Not DOM

Pretext separates text layout into two phases:

  1. Measurement — done once per (text, font) pair, using the browser's actual font engine via an off-screen canvas's measureText(). This produces a small numeric "fingerprint" of the text: glyph widths, break opportunities, segment boundaries.
  2. Layout — runs on cached measurements as ordinary arithmetic. No DOM queries. No reflows. No layout thrashing.

The numbers are striking. For 500 text blocks:

Approach Time DOM Operations
getBoundingClientRect() per block ~19ms 500 reflows
Pretext layout() per block ~0.09ms 0

That's roughly 200x–500x faster depending on hardware and scenario — not because the code is cleverer, but because an entire class of expensive operations has been removed from the hot path.

Four Problems Pretext Actually Solves

The engine isn't an academic exercise. It targets concrete pain points that show up in real production codebases.

1. Virtual Lists That Never Jitter

Virtual scrollers — the kind used in chat clients, log viewers, infinite feeds — all share one weakness: variable-height rows. To position row N you need to know how tall rows 0..N-1 are, and that means measuring text. Every measurement is a reflow, every reflow is a frame budget casualty.

With Pretext, you measure once with prepare() and then call layout() for each row width as the user resizes or scrolls. Heights are exact, scroll position is stable, and there's zero DOM round-trip cost.

2. Custom Layouts That CSS Can't Express

Masonry grids, flowing text around dragged sprites, magazine-style multi-column layouts that respect arbitrary obstacles — these require knowing line-by-line geometry before committing to a layout. CSS can't tell you "how tall would this paragraph be at width X" without actually rendering it. Pretext can, in microseconds, for any number of trial widths.

walkLineRanges() and layoutNextLine() make iterative and variable-width layouts practical, including binary-searching for the optimal container width.

3. Catching Overflow Before the Browser Does

Designers care about whether a label fits before it gets clipped. With DOM measurement, you can only check after the fact — and only on devices identical to your test machine's font stack. Pretext lets you assert layout invariants in unit tests, in build scripts, or live in your design tooling, using exactly the font metrics the browser would use.

4. Eliminating Cumulative Layout Shift From Text

CLS — Cumulative Layout Shift — is one of Google's Core Web Vitals, and a major source of it is text that loads asynchronously and bumps everything down. If you know the final height up front (because you measured it without rendering it), you can reserve space exactly. No skeleton-screen guessing, no wrong placeholders, no jumpy layout.

The API in Five Functions

The full surface area is small enough to memorize:

import { prepare, layout, layoutWithLines, walkLineRanges, layoutNextLine } from '@chenglou/pretext';

// 1. One-time measurement
const prepared = prepare("Hello, world — this is Pretext.", "18px Georgia");

// 2. Hot-path layout: returns just height + line count
const { height, lineCount } = layout(prepared, 400, 1.5);

// 3. Need per-line data for rendering?
const { lines } = layoutWithLines(prepared, 400, 1.5);

// 4. Iterate lines without building strings
walkLineRanges(prepared, 400, (start, end, lineIndex) => { /* ... */ });

// 5. Variable widths (e.g., flowing around an obstacle)
const line = layoutNextLine(prepared, lastEnd, currentMaxWidth);

prepare() is the only "expensive" call — and even then, ~19ms for 500 distinct strings on a laptop. layout() is fast enough to call inside requestAnimationFrame for every visible row.

What the Engine Handles

Pretext is built for the messy reality of global typography:

It's not trying to be a full font rendering stack — it's a layout engine. Render targets are your choice: DOM, <canvas>, SVG, WebGL, or server-side via Node.

Why "AI-Friendly" Matters

A subtle but important Pretext property: layout is deterministic and inspectable from JavaScript. That means an LLM (or any code generator) can produce a UI, ask Pretext "would this text overflow?", and iterate — no headless browser, no screenshot-based feedback loop. For automated UI generation, layout-aware code review, or design-system linting, that's a step change.

Install in 10 Seconds

npm install @chenglou/pretext

No DOM, no peer dependencies, no framework lock-in. It works the same in React, Vue, Svelte, Solid, or plain JavaScript.

See It Running

The best demonstration of why this matters is what people have built with it. The pretext.cool community showcase currently hosts 18+ interactive demos — typographic Tetris and Breakout, real-time text reflow around dragged sprites, the Star Wars opening crawl with correct perspective, magazine layouts flowing around animated clocks, face-tracked typography that responds to your webcam.

These aren't possible with traditional DOM measurement. They're not possible with CSS alone. They're possible because text layout finally got fast enough to be a per-frame primitive.

If you want to start building, the getting started guide walks you through your first layout in under five minutes, and the Pretext playground lets you try the API live in the browser.


Inspired by community write-ups about Pretext. Library by chenglou — community showcase at pretext.cool.

More Guides

Try These Demos

← Browse all 20 demos