●  Live · 3 flagships · 3 experiments · 7 retired · updated Sep 2026
EST. 20241 makerBuilt in public
EXP-002 · Demo · Lab experiment / @chenglou/pretext

Pretext playground.

Fast paragraph height prediction, multilingual line breaking, shrinkwrapped chat bubbles and responsive columns — measured without DOM reflow.

Featured demos
Bubbles, columns, accordions

Inspired by the upstream demos that make Pretext instantly legible for designers and engineers.

Why it matters
Stable UI under pressure

Useful for virtualization, hand-built layouts, mixed bidi content, and dynamic interfaces that need predictable text geometry.

Use case 01

Predicted block height

The container below sizes itself from layout(). Change width or line height and the preview updates without a DOM measurement pass.

cold prepare
measured width / 360px line-height / 30px

Use case 02

Manual SVG-ready lines

This panel uses prepareWithSegments(), layoutWithLines(), and walkLineRanges() so each line can be styled independently.

Use case 03

Dynamic editorial flow around an orb

The layout below calls layoutNextLine() one row at a time. Lines beside the orb get a narrower width; lines below it expand back out automatically.

routing lines

Demo 04

Shrinkwrapped chat bubbles

Each bubble uses walkLineRanges() to shrink to its tight multiline width instead of wasting space with one blunt max width.

measuring bubbles

Demo 05

Responsive multi-column layout

A single prepared text stream flows across one, two, or three columns depending on width, using layoutNextLine() instead of CSS columns.

routing columns

Demo 06

Auto-growing textarea

This composer grows from Pretext’s measured height in pre-wrap mode — no hidden mirror element and no DOM probing loop.

typing surface

Demo 07

Measured accordion panels

Each accordion body height is computed up front from text width and line height, so the open panel feels deliberate instead of approximate.

prepare()

Analyze once

Normalize whitespace, segment multilingual text, and cache measured runs so width changes stay cheap.

walkLineRanges()

Probe geometry

Find the widest rendered line and inspect breakpoints without materializing every line string first.

layoutNextLine()

Route around shapes

Great for editorial layouts, mixed canvases, SVG scenes, and hand-built typography systems.

EXP-002 · Lab demo · About

What this page is demonstrating

This is EXP-002, a mosaic/lab experiment: a live playground for @chenglou/pretext (version 0.0.3, vendored under /vendor/pretext), the open-source JavaScript library by Cheng Lou for fast multiline text measurement. It exists for credibility, not traffic — a working proof that this lab reads other people's source code and puts it on stage.

The problem Pretext solves is concrete: measuring text with the DOM — getBoundingClientRect, offsetHeight, hidden mirror divs — forces synchronous layout reflow, one of the most expensive operations in the browser. When many components each measure their own text, every read can reflow the whole document, which is how interfaces end up spending tens of milliseconds per frame just asking how tall a paragraph is.

Pretext answers in two phases. prepare(text, font) segments the text with Intl.Segmenter, measures each word once through canvas measureText, and caches the widths — the one expensive pass. layout(prepared, maxWidth, lineHeight) then walks those cached widths with pure arithmetic to return line count and height, cheaply, on every resize. Nothing is mounted in the DOM to get an answer.

API reference · Used on this page

The seven calls behind the demos

Every demo below is wired to the real library — the page imports these seven functions and nothing else shapes text. The two-phase rhythm is always the same: prepare once, lay out as often as you like.

API Role
prepare()One-time analysis pass: normalize whitespace, segment multilingual text, measure and cache word widths.
layout()Cheap hot path: paragraph height and line count for a width and line height, from cached widths.
prepareWithSegments()Rich variant of prepare that keeps segment metadata, for per-line rendering to SVG, canvas, or DOM.
layoutWithLines()Returns each line's text and measured width, so lines can be drawn or styled independently.
walkLineRanges()Probes line geometry — e.g. the widest rendered line — without materializing every line string first.
layoutNextLine()Iterator-style layout: one line at a time, each with its own width — the key to flows around shapes and columns.
clearCache()Drops measurement caches; the Clear cache button above calls it, forcing the next pass to be a cold prepare.

Demo guide · What each one proves

Reading the seven demos

01 · Predicted block height. The preview box sizes itself from layout(). Drag the width or line-height sliders and the box updates with no DOM measurement pass — the height was computed, not read back from layout.

02 · Manual SVG-ready lines. Built on prepareWithSegments(), layoutWithLines(), and walkLineRanges(), so every line arrives as its own string plus width and can be drawn anywhere — here, as SVG rows with per-line pills.

03 · Orb flow. Calls layoutNextLine() one row at a time: lines beside the orb get a narrower width, lines below it expand back out. Editorial wrap-around-float behavior with zero floats and zero reflow.

04 · Shrinkwrap bubbles. Each chat bubble uses walkLineRanges() to find its widest line and shrink to the real multiline width. The header pill reports pixels saved versus a blunt one-size max width.

05 · Columns. One prepared text stream flows across one, two, or three columns depending on panel width, routed line by line with layoutNextLine() — responsive multi-column layout without CSS columns or hidden mirrors.

06 · Auto-grow textarea. The composer sizes itself from Pretext's measured height in pre-wrap mode. The classic hidden-mirror-element trick is simply absent.

07 · Accordion. Each panel body height is computed up front from text width and line height, so opening a section animates to an exact pixel value instead of an estimate.

One accuracy detail worth copying: this demo declares the Inter font explicitly in both CSS and every Pretext font shorthand, per the library's accuracy guidance — measured fonts and rendered fonts must match, or predictions drift.

FAQ · Honest answers

Questions this demo raises

How is Pretext different from canvas measureText or a hidden mirror div?

Raw canvas measureText only measures single runs — it knows nothing about wrapping, whitespace collapsing, or bidirectional text. Hidden mirror divs know all of that but force DOM reflow on every read, which is exactly the cost that stutters lists and animations. Pretext sits between: a full line-breaking pipeline (segmentation, bidi handling, whitespace rules per the CSS white-space modes it supports) built on canvas measurement, so prepare() runs once and layout() answers with arithmetic.

Does it handle bidirectional and multilingual text?

Yes — that is one of its reasons to exist. Segmentation runs through Intl.Segmenter, which covers per-character CJK breaking, Thai, Arabic, and similar scripts, and the rich path carries bidi metadata for mixed left-to-right and right-to-left runs. This page's default sample deliberately mixes English, Arabic, Japanese, and emoji so you can watch mixed-script copy stay stable while you drag the sliders.

When should I not use Pretext?

Three honest cases. First, simple static text: one DOM read is cheaper than a new dependency, so a label that never moves does not need a measurement library. Second, unloaded fonts: predictions are only as good as the font match, so text rendered in a web font that has not finished loading will measure wrong — wait for the font or accept the drift. Third, server-side rendering: the library measures with browser font machinery and does not support Node rendering yet, so keep it client-side.