Pretext (@chenglou/pretext) is a TypeScript library for measuring and laying out text without the DOM. This guide walks you through installation, your first layout, and rendering to the screen — all in under 5 minutes.
Pretext is published on npm as @chenglou/pretext. Install it with your preferred package manager:
# npm
npm install @chenglou/pretext
# pnpm
pnpm add @chenglou/pretext
# yarn
yarn add @chenglou/pretext
Pretext ships with full TypeScript type definitions out of the box — no separate @types package needed. It works in any modern browser and in Node.js (with a canvas polyfill like node-canvas or @napi-rs/canvas).
Pretext is written in TypeScript and exports typed APIs. No extra configuration is required:
import { prepare, layout, layoutWithLines } from '@chenglou/pretext';
// All functions are fully typed
const prepared = prepare("Hello, Pretext!", "16px Georgia");
const result = layout(prepared, 300, 1.5);
// result: { height: number, lineCount: number }
If you're using JavaScript without TypeScript, the same imports work — you just won't get type checking.
Here's a complete example that measures text and logs the result:
import { prepare, layout } from '@chenglou/pretext';
// Step 1: Measure the text (slow, do once)
const prepared = prepare(
"The quick brown fox jumps over the lazy dog. " +
"This sentence is long enough to wrap across multiple lines.",
"18px Georgia, serif"
);
// Step 2: Layout at a given width (fast, can repeat every frame)
const { height, lineCount } = layout(prepared, 400, 1.5);
console.log(`Text wraps to ${lineCount} lines, total height: ${height}px`);
Key insight: prepare() does the expensive font measurement work once. After that, layout() is pure arithmetic and runs in ~0.09ms — fast enough for every animation frame.
Pretext computes layout but doesn't render anything. Here's how to render to the DOM:
import { prepare, layoutWithLines } from '@chenglou/pretext';
const text = "Pretext measures text without the DOM. It's 500x faster than getBoundingClientRect() for batch text measurement. Perfect for dashboards, editors, animations, and games.";
const prepared = prepare(text, "16px system-ui, sans-serif");
const { lines } = layoutWithLines(prepared, 500, 1.5);
const container = document.getElementById('output')!;
container.style.position = 'relative';
lines.forEach((line, i) => {
const div = document.createElement('div');
div.textContent = line.text;
div.style.position = 'absolute';
div.style.top = `${i * 24}px`; // 16px * 1.5 lineHeight
container.appendChild(div);
});
For games, animations, or high-performance rendering:
import { prepare, layoutWithLines } from '@chenglou/pretext';
const canvas = document.getElementById('canvas') as HTMLCanvasElement;
const ctx = canvas.getContext('2d')!;
const text = "Text laid out by Pretext, rendered on canvas.";
const fontSize = 18;
const lineHeight = 1.5;
const prepared = prepare(text, `${fontSize}px Georgia`);
const { lines } = layoutWithLines(prepared, canvas.width, lineHeight);
ctx.font = `${fontSize}px Georgia`;
ctx.fillStyle = '#333';
let y = fontSize; // baseline offset
lines.forEach(line => {
ctx.fillText(line.text, 0, y);
y += fontSize * lineHeight;
});
Because layout() is so fast, you can re-layout on every resize without debouncing:
import { prepare, layoutWithLines } from '@chenglou/pretext';
const prepared = prepare(longText, "16px Georgia");
function render() {
const width = container.offsetWidth;
const { lines, height } = layoutWithLines(prepared, width, 1.5);
// ... render lines
container.style.height = `${height}px`;
}
window.addEventListener('resize', render);
render();
import { useMemo } from 'react';
import { prepare, layoutWithLines } from '@chenglou/pretext';
function PretextBlock({ text, width }: { text: string; width: number }) {
const prepared = useMemo(() => prepare(text, "16px Georgia"), [text]);
const { lines } = layoutWithLines(prepared, width, 1.5);
return (
<div style={{ position: 'relative', height: lines.length * 24 }}>
{lines.map((line, i) => (
<div key={i} style={{ position: 'absolute', top: i * 24 }}>
{line.text}
</div>
))}
</div>
);
}
Pretext is the right choice when you need to measure or lay out text frequently or in batch — situations where DOM measurement becomes a bottleneck:
For a simple blog page with static text, the browser's built-in layout is fine. Pretext shines when measurement happens hundreds or thousands of times.
prepare() and layout() internalsPretext library by chenglou. Community showcase at pretext.cool.