JavaScript · · 8 min read

The Layout Reflow That Didn't Have To Happen

For twenty-five years I've accepted that measuring text requires the DOM. Pretext is a 15KB library that proves otherwise.

For twenty-five years I've been building websites, and for twenty-five years I've accepted a fundamental rule of the web: if you want to know how tall a block of text is, you have to render it and ask the browser. You create a hidden element, stick your text in it, append it to the DOM, read the computed height, and tear it down. Every single time, the browser has to recalculate the position of everything on the page. That's called a layout reflow, and it is expensive.

I never questioned it. It was just how things worked. Like gravity, or IE6's box model.

Then Cheng Lou dropped Pretext, and now I'm questioning everything.

What Pretext Actually Does

Pretext is a tiny JavaScript library (about 15KB, zero dependencies) that measures and lays out multiline text without ever touching the DOM. No getBoundingClientRect(). No offsetHeight. No hidden elements. No reflow.

It works by using the browser's canvas.measureText() API, which taps into the same font engine as DOM rendering but operates completely outside the layout tree. Pretext measures your text segments once via canvas, caches the glyph widths, and from that point forward, every layout calculation is pure arithmetic over those cached numbers.

The API is absurdly simple. Two functions for the basic case:

import { prepare, layout } from '@chenglou/pretext'

// One-time: segment text, measure glyphs, cache everything
const prepared = prepare('Your text here', '16px "Source Sans 3"')

// Instant: pure arithmetic, call as many times as you want
const { height, lineCount } = layout(prepared, containerWidth, lineHeight)

prepare() does the heavy lifting once. layout() is the hot path, and it's basically free. Change the container width? Just call layout() again with the new number. Same cached handle. Same microsecond response.

Building the Demo

I built a demo to see this in action, and it has two parts.

The Responsive Height Predictor lets you type text, drag a width slider, change fonts, and watch Pretext calculate height and line count in real time. Next to it, the same measurement runs through traditional DOM getBoundingClientRect() for comparison. When you're only adjusting the width slider (so prepare() is cached and only layout() runs), the Pretext time drops to 0.0 microseconds. Not a typo. It's faster than performance.now() can measure.

The Pre-measured Masonry Cards take 12 content cards in English, Chinese, Arabic, French, Korean, Japanese, and mixed Unicode with emoji, and lay them out in a masonry grid. Every card height is predicted by Pretext before a single card element hits the DOM. The shortest-column-first algorithm distributes cards using those predicted heights. Change the column count or font size and the whole grid recalculates instantly. Twelve cards, 24 prepare() calls (title plus body each), and it takes around 2 milliseconds total.

Live Demo Open in CodePen ↗

The multilingual content isn't just for show. Pretext handles Unicode segmentation and bidirectional text automatically. The same two-function API works whether you're measuring English, Chinese, Arabic, or a sentence that contains all of them plus emoji.

What I Learned

The prepare / layout split is the whole insight. It maps perfectly to how real UIs work. Text content changes infrequently. Container widths change constantly: window resize, responsive breakpoints, sidebar toggles, accordion expand/collapse. By separating the expensive text analysis from the cheap layout arithmetic, Pretext lets you pay the cost once and reflow for free forever.

Canvas measurement is the clever bit. The browser's canvas.measureText() uses the exact same font engine as DOM rendering, but it doesn't trigger layout. It's been sitting there in every browser for years, and nobody thought to build a full text layout engine on top of it. Pretext handles all the messy details: word segmentation, soft hyphens, non-Latin character sequences, emoji, whitespace normalization, and the browser-specific quirks that make this harder than it sounds.

The timing numbers are real but need context. On first load, prepare() takes 5–15 milliseconds depending on text length and complexity. That's the canvas measurement cost. After that, layout() is sub-microsecond. The demo shows this clearly: type new text and the Pretext timer spikes. Drag the width slider and it drops to zero. That contrast is the story.

DOM measurement is not as slow as you'd think for a single call. One getBoundingClientRect() on an already-rendered hidden element is pretty fast. The problem is at scale: virtual scroll lists with hundreds or thousands of variable-height items, chat interfaces with streaming messages, masonry grids that recalculate on resize. That's where 500 reflows become 0 reflows, and the 300–600x speedup becomes real.

Where This Actually Matters

I work on VA digital services, building interfaces with the VA Design System and USWDS. Here's where Pretext would solve real problems:

Accordion and expandable sections. VA.gov is full of expandable content sections. Currently, when you toggle one open, the browser has to render the content to know its height for a smooth animation. With Pretext, you could predict the expanded height before the animation starts. No layout shift, no content popping.

Chat and messaging interfaces. VA is building more conversational UI patterns, including AI-powered chatbots for Veteran support. Chat bubbles with variable-length messages are exactly the use case Pretext was born for. Pre-compute bubble heights before the message finishes streaming. No jumpiness.

Alert and notification banners. Government sites love banners: warning banners, info banners, emergency banners, sometimes stacked three deep. Each one has variable text that wraps differently at different viewport widths. Pretext could pre-calculate the total banner height so the page content below doesn't shift as banners load.

Virtual scrolling for long lists. Claims status, appointment history, message threads. These are all long lists of variable-height items. Virtual scroll libraries need to know item heights before rendering them. Currently that means pre-rendering hidden elements and measuring. Pretext replaces that entire pattern with arithmetic.


Cheng Lou has a pattern. With react-motion, he questioned why animations needed duration and easing curves, then replaced them with physics springs. With Pretext, he's questioning why text measurement needs the DOM at all. The library is MIT licensed, framework-agnostic, and works with vanilla JS, React, Vue, Svelte, or anything else. It renders to DOM, Canvas, SVG, or WebGL.

For anyone building interfaces where layout performance matters, this is worth your time. Install it, call two functions, and stop triggering reflows you never had to trigger.

Pretext on GitHub →