Pretext and CSS: What CSS Text Layout Can't Do (and How Pretext Fills the Gap)

Apr 3, 2026

CSS is the most powerful text rendering system ever built. It handles font shaping, bidirectional text, word breaking, hyphenation, and a thousand other typographic details across every language on Earth. But there is one thing CSS fundamentally cannot do: tell you how tall text will be before it renders.

This single limitation creates an entire class of layout problems that web developers struggle with every day. Pretext exists to fill that gap — computing text layout in pure JavaScript, using the same rules CSS uses, but without ever touching the DOM.

What CSS Text Layout Does Well

Before diving into the gaps, it is worth acknowledging what CSS does brilliantly:

  • Font rendering: Sub-pixel antialiasing, font fallback chains, variable fonts, OpenType features
  • Text shaping: Complex scripts like Arabic, Devanagari, and Thai are shaped correctly
  • Line breaking: The word-break, overflow-wrap, and line-break properties give you fine control over where lines split
  • White-space handling: white-space: normal, pre, pre-wrap, pre-line — each with well-defined collapsing and wrapping rules
  • Internationalization: Bidirectional text, vertical writing modes, CJK punctuation rules

CSS is battle-tested across billions of devices. Nothing replaces it for final rendering.

The Three Things CSS Cannot Do

1. Pre-Render Height Calculation

The most common problem. You have a string, a font, and a container width. You need to know the resulting height — but CSS will not tell you until the text is in the DOM and the browser has reflowed.

// The traditional "measure by rendering" hack
const el = document.createElement('div');
el.style.cssText = 'position:absolute;visibility:hidden;width:300px;font:16px/1.5 sans-serif';
el.textContent = text;
document.body.appendChild(el);
const height = el.offsetHeight; // forces reflow
document.body.removeChild(el);

This works, but every call forces a synchronous reflow. In a virtual scroll with 10,000 items, that is 10,000 reflows — each one blocking the main thread.

With Pretext:

import { prepare, layout } from 'pretext';

const prepared = prepare(text, { font: '16px/1.5 sans-serif' });
const result = layout(prepared, 300);
console.log(result.height); // instant, no DOM

One prepare() call, then layout() is pure arithmetic. Measure 10,000 items in under a millisecond.

2. Shrink-Wrap Width

You want the narrowest container width that does not add extra line breaks. CSS has never supported this in 30 years. The min-content value gives you the width of the longest word, and max-content gives you the width of the entire text on one line. Neither gives you the tightest multi-line wrap.

This matters for chat bubbles, tooltips, and captions — anywhere you want text to wrap naturally without wasted horizontal space.

// Pretext computes the shrink-wrap width directly
const result = layout(prepared, containerWidth);
const tightWidth = result.width; // narrowest width preserving line count

No iterative binary search. No trial-and-error rendering. Just the answer.

3. Layout Without a Browser

CSS requires a browser engine. If you are running on a server, in a Web Worker, or in a Node.js process, there is no DOM and no CSS engine. Traditional solutions involve headless browsers or canvas — both heavyweight and slow.

Pretext runs anywhere JavaScript runs. Server-side rendering, edge functions, build-time static generation — all with pixel-accurate text layout.

How Pretext Maps to CSS Properties

Pretext does not replace CSS — it mirrors the CSS text layout model so that its measurements match what the browser will render. Here is how the properties correspond:

CSS PropertyPretext EquivalentDefault
font (shorthand)fontRequired
white-spacewhiteSpace'normal'
word-breakwordBreak'normal'
overflow-wrapoverflowWrap'break-word'
line-breaklineBreak'auto'
letter-spacingletterSpacing0
word-spacingwordSpacing0
text-indenttextIndent0

The key insight: if your Pretext font string matches your CSS font shorthand, the computed heights will match. Pretext reads the same font metrics the browser uses.

// CSS
.message {
  font: 400 16px/1.5 "Inter", sans-serif;
  white-space: pre-wrap;
  word-break: break-word;
  width: 320px;
}

// Pretext — same properties, same result
const prepared = prepare(text, {
  font: '400 16px/1.5 "Inter", sans-serif',
  whiteSpace: 'pre-wrap',
  wordBreak: 'break-word',
});
const { height } = layout(prepared, 320);

Practical Examples

Virtual Scroll with Accurate Heights

CSS-based virtual scroll libraries estimate row heights or measure them lazily, causing scroll bar jumps and content shifting. Pretext eliminates this:

const items = messages.map(msg => {
  const prepared = prepare(msg.text, { font: '14px/1.6 system-ui' });
  const { height } = layout(prepared, containerWidth);
  return { ...msg, height: height + padding };
});

// Pass exact heights to your virtual scroll library
<VirtualList items={items} itemHeight={item => item.height} />

Every row height is known before the first paint. The scroll bar is accurate from the start.

Tight-Wrap Chat Bubbles

Most chat UIs set a max-width and let bubbles stretch to it. Short messages waste space. Pretext computes the tightest width:

const prepared = prepare(message, { font: '15px/1.4 system-ui' });
const { width, height } = layout(prepared, maxBubbleWidth);

// The bubble wraps tightly around the text
<div style={{ width, height, padding: '8px 12px' }}>
  {message}
</div>

Server-Side Layout for Email or PDF

CSS does not exist on the server. Pretext does:

// In a Node.js API route or edge function
import { prepare, layout } from 'pretext';

function computeEmailLayout(blocks, columnWidth) {
  return blocks.map(block => {
    const prepared = prepare(block.text, { font: block.font });
    return { ...block, ...layout(prepared, columnWidth) };
  });
}

No headless browser. No Puppeteer. Just math.

When to Use CSS vs Pretext

Use CSS when you are rendering text normally and the browser handles layout. CSS is faster for final rendering because it is native code with GPU acceleration.

Use Pretext when you need to know text dimensions before rendering, or when you need layout without a browser:

  • Virtual scrolling with accurate heights
  • Chat bubble sizing
  • Editor layout engines
  • Server-side or Worker-based layout
  • Animation planning (knowing where text will land before it moves)
  • Canvas or WebGL text rendering

Pretext and CSS are complementary. Pretext computes the layout. CSS renders it. Together they solve problems neither can handle alone.

Getting Started

npm install pretext
import { prepare, layout } from 'pretext';

// 1. Prepare the text (one-time cost)
const prepared = prepare('Hello, world!', {
  font: '16px/1.5 sans-serif',
});

// 2. Layout at any width (near-free)
const result = layout(prepared, 200);
console.log(result.height); // exact pixel height
console.log(result.width);  // shrink-wrap width
console.log(result.lines);  // line break positions

The prepare step handles text segmentation and font measurement. The layout step is pure arithmetic — call it thousands of times at different widths with virtually zero cost.

Conclusion

CSS is unmatched for rendering text. But for 30 years it has lacked a way to predict text dimensions before rendering. Pretext fills that gap with a pure JavaScript engine that mirrors CSS text properties and produces matching results — in under a millisecond, without a DOM, anywhere JavaScript runs.

If you have ever written a hidden div hack to measure text height, Pretext is the tool you have been waiting for.


Learn more at pretextjs.dev or explore the interactive demos.

Pretext.js Team

Pretext.js Team

Pretext and CSS: What CSS Text Layout Can't Do (and How Pretext Fills the Gap) | Blog