Getting Started with Pretext: Install, Set Up, and Render Your First Text Layout

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.

Install

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).

TypeScript Setup

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.

Your First Layout: Hello World

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.

Rendering to the DOM

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);
});

Rendering to Canvas

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;
});

Responsive Layout (Resize Handling)

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();

React Integration

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>
  );
}

When to Use Pretext

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.

Next Steps


Pretext library by chenglou. Community showcase at pretext.cool.

More Guides

Try These Demos

← Browse all 19 demos