← Writing
TechnicalMarch 31, 20265 min read

Implementing Pretext:
Solving a 30-Year Browser

Problem on a Portfolio Site

Browsers have never had a fast, programmatic way to measure text without triggering layout reflow. Cheng Lou just fixed that with a 15KB library. I put it to work.

The problem with text in browsers

If you have ever needed to know exactly where a character sits on screen, you know the ritual: create an invisible DOM element, set its styles, inject the text, force the browser to reflow, read back the computed dimensions, then tear it down. One measurement costs one reflow. Ten headings cost ten reflows. A portfolio grid with responsive text costs dozens.

This has been the only option since Netscape Navigator. The DOM was never designed for pre-flight text measurement. It was designed to render documents.

Pretext, released March 2026 by Cheng Lou (Midjourney engineer, former React core team, creator of React Motion), sidesteps the DOM entirely. It queries the browser's Canvas font metrics engine and uses pure arithmetic to predict where every character, word, and line will land. No reflow. No invisible elements. No string allocations on the hot path.

Why this mattered for a portfolio site

I had a specific problem. Every case study on this site opens with a large heading rendered in Outfit at clamp(2rem, 5vw, 3.5rem). I wanted character-level entrance animation on those headings: each letter rising gently into place as the page loads, staggered across the title. Not a gimmick. A moment of craft that signals attention to detail.

The standard approach is to wrap every character in a <span>, then animate each span individually. This works, but it means the browser is performing layout on dozens of inline elements per heading, and you lose natural word-wrapping. You end up reimplementing line breaking in JavaScript, or accepting that your heading wraps differently than it would as plain text.

Pretext solves this. It tells you exactly where each character will land at a given container width, accounting for the actual font metrics. I can animate characters to their correct positions without the browser ever laying out the text itself.

Two-phase measurement

Pretext splits work into a prepare phase and a layout phase. The prepare phase is the expensive one: it measures every character segment against the Canvas API and caches the widths. The layout phase is pure math.

Prepare (500 texts)
~19ms
one-time cost, cached
Layout (500 texts)
~0.09ms
runs on every resize

For my use case, the prepare cost is negligible: I measure one heading per page load. The layout cost is what matters. On window resize, recalculating character positions takes fractions of a millisecond instead of triggering reflow.

The implementation

The component is a React client component that takes the title text as a prop. On mount, it waits for fonts to load (critical for accurate measurement with Google Fonts), reads the computed font style from a hidden reference element, then uses Canvas measureText() to calculate character positions.

// Measure each character's x position using Canvas
const ctx = canvas.getContext("2d");
ctx.font = `${fontWeight} ${fontSize} ${fontFamily}`;

for (const char of line) {
  positions.push({ char, x, lineIndex });
  x += ctx.measureText(char).width;
}

Each character gets a staggered CSS transition: 18ms delay per character, a small upward translate, and an opacity fade. The easing is cubic-bezier(0.22, 1, 0.36, 1), which gives a quick start with a gentle settle. Total animation time for a typical heading is under 600ms.

A ResizeObserver watches the container. When the width changes, the component re-measures and replays the animation. The recalculation itself is sub-millisecond.

What I learned

Font loading is non-negotiable. If you measure before Google Fonts finish loading, the Canvas API falls back to a system font and every width is wrong. The document.fonts.ready promise is essential.

Named fonts only. Pretext's README warns that system-ui resolves to different optical variants on macOS depending on whether you query via Canvas or the DOM. Since this site uses Outfit and Inter explicitly, this was not an issue, but it would bite anyone relying on system fonts.

Subtlety is the whole point. The first version had too much vertical travel and too aggressive a stagger. It looked like a SaaS landing page. Dialing it back to 0.15em translate and 18ms per character made it feel like the text was always there and you just caught the last moment of it arriving. That is the difference between pizzazz and performance.

Why it matters beyond animation

Character-level animation is a narrow use case. What Pretext actually unlocks is a class of layout problems that browsers have never solved well: text flowing around arbitrary shapes (not just rectangles), SVG text wrapping (which the spec has lacked since 2003), and off-main-thread text layout via Web Workers.

Cheng Lou built this using iterative AI-assisted development with Claude and Codex, feeding browser ground-truth data and having the models converge on arithmetic that matches real rendering. The test corpus included the full text of The Great Gatsby and multilingual datasets across Thai, Chinese, Korean, Japanese, and Arabic. Simon Willison called the testing methodology “particularly impressive.”

The library is 15KB with zero dependencies. It works everywhere Canvas works. For a problem that has existed since the mid-1990s, that is an unreasonably elegant solution.

PretextTypographyCanvas APIReactNext.jsPerformanceAnimationCheng Lou