I'll be honest. When I first saw pretext making the rounds, my reaction was skepticism. Another library claiming CSS is obsolete? Another "revolutionary" approach that turns out to be a solution hunting for a problem? I've seen enough of those.
But the stars kept climbing, the README was unusually blunt about its own limitations, and the author is Cheng Lou, who built React Motion and has a track record of shipping things that matter. So I did what I do when hype outpaces my understanding. Dig in, benchmark it, work out where this thing earns its keep.
What follows is that investigation. Every benchmark runs live in your browser. Every demo uses the real library. And the verdict lands somewhere messier than "use it" or "don't."
01. The problem
Why measuring text height is surprisingly hard
Here's something most developers don't think about until it bites them: asking the browser "how tall is this text?" is one of the most expensive things you can do.
The moment you ask, the browser stops what it's doing and recalculates the size and position of elements on the page. That process is called layout reflow, and only once it finishes do you get your answer. For one element you'll never notice. Ask for hundreds at once, in a scrolling feed or a chat window or a Pinterest-style grid, and those tiny pauses add up fast.
The interleaving trap
It gets worse if you're not careful. The classic mistake is alternating between changing the DOM and reading measurements in a loop. Each read forces the browser to recalculate everything you just changed before it can answer. Then you change something again, and the cycle repeats.
Even the "smart" approach, saving all your changes first and then reading all your measurements in one batch, still triggers a full recalculation. For 500 items on a resize event that's about 0.18ms in Chrome. Not catastrophic, but it happens on every resize, and it ties your layout code to the browser's rendering schedule.
The real issue isn't speed. It's dependency. To measure text with the DOM, the element has to exist on the page first. You can't measure in a background thread. You can't measure on the server. You can't measure before the component mounts. That coupling is the constraint.
What developers do instead
Most teams just... work around it. Average row heights, close enough. Estimates from character count, not great. Hidden elements to measure against, expensive. Or they accept the jank. Some libraries get clever. canvas-hypertxt, from the Glide team, trains a model to estimate string widths. uWrap uses character-pair kerning tables for Latin text.
These all share one thing: they're approximations. Pretext claims to be exact. Bold claim. Let's find out.
02. How it works
The big idea: skip the DOM entirely
Pretext's core insight is straightforward. Instead of putting text in the DOM and asking the browser to lay it out, it uses the canvas text API (measureText()) to get word widths straight from the font engine. No layout, no reflow, no DOM. It measures everything once, caches the results, and from then on calculates text height with arithmetic.
// Forces layout reflow every call
function getHeight(text, width) {
el.style.width = width + 'px';
el.textContent = text;
return el.getBoundingClientRect().height;
// triggers full layout reflow
}~0.18ms per batch (500 items, resize)
// One-time prep: canvas measureText
const prepared = prepare(text, '16px Inter');
// Hot path: pure arithmetic
const { height } = layout(prepared, width, 24);
// no DOM, no reflow, ~0.0002ms~0.02ms for all 500 items on resize
Phase 1: prepare(), measure once
This is the setup step. Pretext takes your text, breaks it into words and segments with Intl.Segmenter so Chinese, Arabic, and emoji all split correctly, then measures each piece with canvas.measureText(). The results get cached by font. Processing 500 paragraphs takes about 19ms, and you pay that once.
Phase 2: layout(), arithmetic instead of measurement
This is the fast part. Given the cached word widths, a container width, and a line height, pretext just... does arithmetic. It walks through the widths, figures out how many lines the text wraps to, and returns the height. No canvas calls. No DOM. No string processing. Just addition and comparison. All 500 items in 0.09ms.
The 100% accuracy figure covers named fonts with standard CSS wrapping on the library's curated test corpus. Arabic, Thai, and Myanmar have edge cases, covered in Caveats below. One gotcha: don't use
system-uion macOS. Canvas and DOM resolve it to different SF Pro variants at certain sizes, which breaks the measurements. Stick with named fonts like Inter or Helvetica Neue.
03. Live benchmark
See for yourself
This is where I stopped being skeptical. The numbers below aren't cherry-picked from a README. They run live in your browser, on the real DOM APIs and pretext's actual code. Hit Run and see what your machine does.
"DOM batched" is the best-case DOM strategy: all writes first, then all reads in one pass. "DOM interleaved" is the common mistake of reading after each write. "Pretext layout()" is the hot-path cost after the one-time prepare() step.
What happens when the window resizes?
A single measurement pass is one thing. In practice the number that matters is the resize cost, what happens every time someone drags their browser window. With the DOM you re-measure everything from scratch. With pretext you call layout() again with the new width. Same cached data, different arithmetic. The gap widens with every resize event.
04. Real-world scenarios
Beyond synthetic benchmarks
Numbers in a chart are nice. I wanted to see pretext solve the problems that have cost me real time in production, not toy demos. Here are four scenarios I tested, each with a live demo.
Virtualised lists with variable-height rows
Virtual scrolling renders only what's visible, offsetting content to simulate a large list. The hard part: computing each row's height to know the total scroll height and which rows are currently visible. With fixed-height rows this is trivial. With text of varying length it has always required either sampling the DOM or accepting wrong estimates.
The conventional approach uses a ResizeObserver or a one-time measurement pass with hidden elements. Both need the elements to exist in the DOM before you know their height, which forces a two-phase render or eager DOM population. Pretext breaks that dependency.
// Must pre-render items to measure
const heights = items.map(item => {
ghost.textContent = item.text;
return ghost.getBoundingClientRect().height;
// Reflow for every item
});
totalHeight = heights.reduce((a,b) => a+b);// Measure once, reuse forever
const prepared = items.map(item =>
prepare(item.text, font)
);
const heights = prepared.map(p =>
layout(p, columnWidth, lineH).height
);
// No DOM. Runs in a Worker.05. Impossible things
Things pretext can do that CSS literally cannot
This is the section that changed my thinking. Pretext isn't a faster way to do what CSS already does. It does things with no CSS equivalent at all. The browser's layout engine is powerful, and you can't program it. Pretext turns text layout into a function call, and that opens doors.
06. Caveats
The honest trade-offs
Time to put the skeptic hat back on. Every tool has limits, and you should know these before you adopt pretext.
It only models one CSS mode
Pretext currently assumes white-space: normal, word-break: normal, overflow-wrap: break-word, and line-break: auto. If your text uses nowrap, break-all, custom tab sizes, or CSS columns, the predictions will be wrong.
Your CSS font must match exactly. Pass the same font string to
prepare()that you use in CSS:"600 16px/1 Inter", not just"Inter". Weight, size, and line height all affect character widths. A mismatch produces silently wrong results, which is the worst kind of bug.
system-ui is broken on macOS
Canvas and DOM resolve system-ui to different SF Pro variants (Text vs Display) at certain sizes on macOS. The mismatch can reach 14.5%. Just use a named font and you're fine.
Multilingual accuracy has gaps
The 100% accuracy claim (7,680 tests across 4 fonts, 8 sizes, 8 widths, 30 texts) is real. The corpus is also curated. Some known edge cases:
- Arabic. Clean on coarse tests, ~7 out of 601 edge cases remain in fine sweeps.
- Japanese. Narrow-width font compression, not a segmentation bug.
- Myanmar. Chrome and Safari disagree on closing-quote and follower cluster behavior.
- Thai. 59/61 on the coarse sweep, two tiny misses remain.
- Hebrew. Not covered in the accuracy sweeps at all. The library has BiDi support with heuristic direction detection, but mixed Hebrew and English text, which is everywhere in Israeli tech, is uncharted. Test it yourself if that's your stack.
Rich text takes more work
Out of the box, pretext measures a single run of text in a single font. Mixed formatting, so bold words, links, and inline code, isn't built in. It is possible: the official rich-note demo prepares each styled span with its own font and flows them together with layoutNextLine(). It works, but you manage per-span preparation and fragment assembly yourself. Doable, not trivial.
Canvas rendering hurts accessibility
This is the caveat that deserves more attention. Use pretext for measurement only, predicting heights for virtual scrolling, masonry, or shrinkwrap, and your text still lives in the DOM as regular HTML. Screen readers, text selection, find-in-page, user font preferences, all fine. Zero accessibility cost.
The moment you render text to Canvas, as the typography demo does, all of that disappears. Canvas text is invisible to assistive technology. It can't be selected, searched, copied, or read aloud. To a screen reader that paragraph doesn't exist.
You can mitigate it with hidden DOM text, aria-label, or transparent overlays the way PDF.js does, and none of that is free. For measurement, pretext is accessibility-neutral. For canvas rendering you're building a custom rendering engine, and you inherit every a11y obligation that comes with one.
It's brand new
As of March 2026, pretext is on npm as @chenglou/pretext (v0.0.3), and it's days old. The first release landed March 26. The API looks stable, and it hasn't seen the volume of production use that shakes out the weird edge cases.
07. When to use it
A practical cheat sheet
After all the testing, here's my take on when pretext justifies the added complexity, and when CSS is the right call.
| Scenario | Use Pretext? | Notes |
|---|---|---|
| Virtual scroll, 100+ variable-height rows | YES | Core use case. Prepare on data load, layout on resize. |
| Chat bubbles with shrinkwrap | YES | CSS cannot do this. walkLineRanges() + binary search. |
| Canvas / WebGL text rendering | YES | layoutWithLines() gives exact line positions for fillText(). |
| Masonry layout (text cards only) | YES | Height prediction before mount. Images still need size data. |
| Editorial / balanced justification | TEST FIRST | layoutNextLine() and layoutWithLines() enable per-line-width algorithms, but Canvas/SVG-rendered text is invisible to screen readers. Extra effort to maintain an accessible DOM fallback may not be worth it. |
| Accordion / expand-collapse animations | TEST FIRST | Works, but CSS height: auto + transition usually suffices. |
| Scroll position anchoring on new content | TEST FIRST | Useful if you need height before insertion. CSS overflow-anchor also helps. |
| Standard text in a fixed-width container | NO | Let the browser lay it out. No reason to override. |
| Rich text (mixed fonts/weights inline) | TEST FIRST | Core API is single-font, but per-span measurement via layoutNextLine() is possible (see rich-note demo). Non-trivial. |
| CSS Grid / Flexbox sizing | NO | Browser layout engine handles this better than any JS. |
| Multilingual app with Arabic/Myanmar/Thai | TEST FIRST | Library has known edge cases in fine sweeps. Validate your specific content. |
How to think about it
Pretext is a companion to your render cycle, not a replacement for CSS. Reach for it when you need to know text dimensions before mounting, or when you're rendering outside the DOM entirely. For everything else, the browser's layout engine is still the best tool for the job.
// Good pattern: prepare() when data arrives
function onDataLoaded(items) {
preparedItems = items.map(item => ({
...item,
prepared: prepare(item.text, '16px Inter'),
}));
updateLayout(containerWidth);
}
// Good pattern: layout() on every resize
function updateLayout(width) {
offsets = [];
let y = 0;
for (const item of preparedItems) {
offsets.push(y);
y += layout(item.prepared, width, 24).height + GAP;
}
totalHeight = y;
}08. Verdict
So... should you use it?
If you're building virtual scrolling, Canvas/SVG text rendering, chat UIs, or any layout that requires knowing text height before mounting, yes, pretext is the best tool available. The performance lead over DOM measurement is real, the accuracy held up on everything I threw at it, and the API is small enough to learn in an afternoon.
If you're building standard content pages, forms, or anything where the browser's own layout engine can do the work, don't reach for pretext. The additional complexity, font-sync discipline, and CSS-mode restrictions aren't worth it when CSS already has the answer.
What changed my mind
I came into this expecting to write a "CSS is fine, you don't need this" article. For most use cases that's still true. CSS is fine, and you don't need this.
But pretext isn't trying to replace CSS. It solves the problems where the browser's layout engine can't help, because the elements don't exist yet, because you're off the main thread, because you need the answer before the DOM is ready. I've hit all three in production and always solved them with hacks. Pretext replaces the hacks with arithmetic.
The library's real contribution isn't speed. It's decoupling text measurement from the DOM lifecycle. That's what makes the "impossible things" possible: worker-side layout, build-time overflow checks, canvas text routing, multiline shrinkwrap.
What to watch
It needs a stable 1.0, framework adapters, and more production mileage before it's an obvious default. Multilingual accuracy should improve as more real text gets thrown at it.
Credits
Cheng Lou built pretext. He also built React Motion and has contributed to the React ecosystem for years. The interactive demos in this article take their cue from the official pretext demos.