TypeScript library for multiline text measurement and layout without DOM reflow.

TypeScript 31k stars MIT

About

Pretext solves a problem that has frustrated web developers for thirty years: there is no reliable way to know how text will wrap before it hits the DOM. The standard approaches — getBoundingClientRect, offsetHeight, querying computed styles — all trigger synchronous layout reflow. Measure 500 text blocks at once and you are looking at 30ms or more per frame, before you have rendered anything.

Pretext takes a different path. It uses the Canvas API's measureText() as a proxy for the browser's font engine, caches the results, and reduces all subsequent layout work to arithmetic on those cached widths. The result is a two-phase design: prepare() does the expensive measurement once, layout() runs in approximately 0.0002ms per block with no DOM access on the hot path.

The library handles the scripts that make browser text layout genuinely hard. Unicode segmentation via Intl.Segmenter handles CJK (where each character is a potential break point), Thai (which lacks spaces between words), and Arabic and Hebrew (which run right-to-left and require the Unicode Bidirectional Algorithm). Seven source files, roughly 15KB of TypeScript, covering most of what a production text layout engine needs to handle.

It is a small library with a precise scope. It does not replace CSS. It gives you line break positions and character offsets as numbers, which you can render however you like — canvas, SVG, custom DOM, WebGL. The decision about what to do with those numbers is yours.

Growing Ecosystem: Beyond the Browser

Five days after Pretext launched, native ports started appearing. swift-pretextkit by Tornike Gomareli brings Pretext's two-phase text measurement to macOS and iOS, with the creator claiming halved CPU and power usage compared to existing options in the Apple ecosystem. The speed of this port signals that Pretext's core insight — separate measurement from layout, cache aggressively — is not browser-specific. It is a general approach to text layout that translates across platforms.

Further Reading

Pretext, and What's Next — an analysis of where Pretext fits in the broader landscape and what the native ports mean for the ecosystem.

Architecture

Two phases, one insight:

prepare(text, options) runs once per unique text string and font configuration. Calls Intl.Segmenter for script-aware segmentation, measures each segment via Canvas measureText(), and stores widths in a two-level cache keyed by font string and segment.

layout(preparedText, containerWidth) is pure arithmetic. Walks the cached widths, applies the greedy line-breaking algorithm, and returns line break positions and character offsets. No DOM. No Canvas calls. No side effects.

FileSizeRole
layout.ts24KBPublic API surface
line-break.ts31KBCore greedy line-breaking algorithm
analysis.ts27KBUnicode script analysis
measurement.ts7KBCanvas measurement and width cache
bidi.ts6KBBidirectional text (Arabic, Hebrew)
layout.test.ts36KBTest suite
test-data.ts5KBGround-truth test fixtures

The test suite is larger than the implementation. That is not an accident — browser behavior around text is inconsistent enough that the tests document a set of known browser discrepancies as much as they verify correctness.

Start here

If you are new to Pretext, start with the Grand Tour. It walks through all seven source files in dependency order, from the public API in layout.ts down to the Canvas measurement cache and the Unicode segmentation logic. You will come out understanding why the two-phase design exists, how the cache is structured, and what the browser inconsistencies documented in RESEARCH.md actually look like in practice.

Tours

Maintainers

Cheng Lou Creator
@chenglou

Currently at Midjourney. Not a newcomer to problems at the intersection of browser constraints and developer experience. His library react-motion (21,700+ stars) replaced the dominant CSS animation approach with physics-based spring animations. Spent several years at Meta working on ReasonML and ReScript, bringing ML-family type systems to the JavaScript ecosystem. His 2016 React Europe talk, "On the Spectrum of Abstraction," laid out a thesis he has returned to since: reducing expressivity through the right constraints yields more capability, not less.

Origin Story

Pretext: Measuring Text Without the DOM

How a 30-year browser problem met a 15KB TypeScript library -- and why 31,000 developers starred it in 25 days.

Read the full story

Related Projects

Your codebase next

Create code tours for your project

Intraview lets AI create interactive walkthroughs of any codebase. Install the free VS Code extension and generate your first tour in minutes.

Install Intraview Free