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.
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.