๐Ÿ”ฎ

The Future of Website Interfaces: Pretext Package

What is this?

Pretext is a single JavaScript/TypeScript package that lets you measure and lay out text without relying on the browser's layout engine.

Every website you've ever used asks the browser to figure out where text goes on the page. That process (called "reflow") is one of the slowest things a browser does. It's been the biggest bottleneck in web design for 30 years.

Pretext skips that entirely. It does all the text measurement in pure TypeScript โ€” no DOM, no CSS layout, no reflow. Just math.

The result: Hundreds of thousands of text elements rendering at 120fps without a single lag.


๐Ÿ”— Links


๐ŸŽฅ What the demos show

DemoWhat it does
MasonryHundreds of thousands of text boxes, all different heights, scrolling at 120fps with zero DOM measurement
Chat BubblesShrinkwrapped chat bubbles that size themselves perfectly
Magazine LayoutMulti-column responsive magazine layout โ€” dynamic and real-time
ASCII ArtVariable font width ASCII art because why not
AccordionAuto-growing text areas, accordions, multiline centering โ€” things that used to be real CSS challenges, now trivial

๐Ÿง  Why this matters (simple version)

  1. Every website asks the browser permission to place text. That permission is slow. It causes lag. It's called "reflow."
  1. Pretext skips that permission entirely. It calculates text layout in pure TypeScript.
  1. The performance difference is insane. 0.05ms vs 30ms. Zero reflows vs hundreds.
  1. This unlocks UI that wasn't possible before. Smooth virtualization, masonry layouts, magazine-style text, shrinkwrapped elements โ€” all without CSS hacks.

โšก Quick Start

Install

npm install @chenglou/pretext

Basic usage โ€” measure text height without touching the DOM

import { prepare, layout } from '@chenglou/pretext'

const prepared = prepare('Your text here', '16px Inter')
const { height, lineCount } = layout(prepared, maxWidth, 20)
// That's it. No DOM. No reflow. Pure math.

How it works

  • prepare() โ€” one-time work: analyzes the text, measures segments using the browser's font engine, returns a cached handle
  • layout() โ€” the fast part: pure arithmetic over cached widths. Run this on resize, scroll, or any layout change

Key rule: Don't rerun prepare() for the same text. That defeats the precomputation. Only rerun layout().


๐Ÿงช Prompts to try with AI

Use these with Claude, ChatGPT, or Cursor to start building with Pretext:

Prompt 1 โ€” Basic integration

"I want to use @chenglou/pretext to measure text height without DOM reflow. Show me how to set up prepare() and layout() for a list of 1000 items with variable text lengths, and use the heights for virtualized scrolling."

Prompt 2 โ€” Masonry layout

"Using @chenglou/pretext, help me build a masonry grid layout where each card contains variable-length text. I want to calculate all card heights upfront without any DOM measurement, then position them in columns using absolute positioning."

Prompt 3 โ€” Chat interface

"I'm building a chat interface. Using @chenglou/pretext and walkLineRanges(), help me shrinkwrap each chat bubble to the exact width of its longest line instead of using a fixed max-width. I want the bubbles to feel tight and natural."

Prompt 4 โ€” Canvas rendering

"Using @chenglou/pretext's layoutWithLines(), help me render multiline text directly to an HTML canvas with proper line breaking and word wrapping. I want to bypass DOM text rendering entirely."

Prompt 5 โ€” Magazine layout

"Help me build a responsive multi-column magazine-style text layout using @chenglou/pretext's layoutNextLine(). The text should flow around an image floated in the first column, with narrower line widths beside the image and full-width lines below it."

๐Ÿ“– Full API Reference

Use case 1: Measure height only (fastest)

prepare(text, font, options?)
// Returns opaque PreparedText handle

layout(prepared, maxWidth, lineHeight)
// Returns { height, lineCount }

Use case 2: Manual line layout (full control)

prepareWithSegments(text, font, options?)
// Returns richer structure for manual layout

layoutWithLines(prepared, maxWidth, lineHeight)
// Returns { height, lineCount, lines[] }

walkLineRanges(prepared, maxWidth, onLine)
// Calls onLine per line with width + cursors โ€” no string building

layoutNextLine(prepared, start, maxWidth)
// Iterator-style โ€” one line at a time with variable widths

Helpers

clearCache()
// Clears internal caches if cycling through many fonts

setLocale(locale?)
// Sets locale for future prepare() calls

โš ๏ธ Things to know

  • Currently targets standard text setup: white-space: normal, word-break: normal, overflow-wrap: break-word
  • Supports { whiteSpace: 'pre-wrap' } for textarea-like text with preserved spaces, tabs, and line breaks
  • Avoid system-ui font on macOS โ€” use a named font for accuracy
  • Supports all languages including emoji and mixed bidirectional text
  • Not a full font rendering engine (yet) โ€” it's focused on measurement and layout

๐Ÿ‘จโ€๐Ÿ’ป Who built this

Cheng Lou โ€” helped build React at Facebook, created ReasonML and ReScript, built Messenger's frontend, currently runs Midjourney's entire UI stack. Every role was a fight against the browser's rendering pipeline. This is the result.