Pretext Lab
09Tabs
Arc 3 · Whitespace & Breaks

Tabs

The last piece of whitespace Pretext takes seriously is the humble \t. Under whiteSpace: 'pre-wrap', a tab advances to the next tab stop — the same rule the browser applies with tab-size: 8 — measured in canvas-width multiples of a single space glyph. The result is an old idea made usable again: columns without a table.

Below is a contemplative ledger. Items on the left, poetic costs on the right, separated by tabs. Try more or fewer tab stops per row.

height — · lines —
440

Mechanism

When prepare() runs with whiteSpace: 'pre-wrap', \t survives as a tab segment. Unlike normal text segments, a tab has no fixed width — its advance is computed at layout() time from the current line's running width: it expands to the distance that brings the running total to the next multiple of the tab stop.

The tab stop itself is 8 spaces wide at the prepared font's space-glyph width. That's the default browser rule (tab-size: 8), and it's the value Pretext uses. So the column a tab advances to depends on everything that came before it on the line — which is exactly why tabs are the right tool for aligning ragged content to a grid without building a table.

If the line wraps, the next line starts fresh at position zero, and tab advances on that line are computed from there. The advance is measurement, not magic.

Application

One engine handles prose, verse, and ledgers. Concretely:

Pretext gives you the exact height of the wrapped result, so aligned listings still get the two-phase dance: measure once, fit forever.

"Our life is frittered away by detail. An honest man has hardly need to count more than his ten fingers, or in extreme cases he may add his ten toes, and lump the rest."

Henry David Thoreau, Walden (1854)

Direct Claude

"aligned columns without a table" pre-wrap + \t between the fields "tabular figures" monospace font + font-variant-numeric: tabular-nums "receipt-style line items" tabs to push prices to the right margin "preserve the indent from the source" pre-wrap handle, feed the original text verbatim
Arc 3 ends here. The next arc crosses into multilingual territory — RTL scripts, CJK keep-all in real passages, emoji graphemes.
a ledger that fits, without a table
import { prepare, layout } from '@chenglou/pretext';

// Tab-separated items. Pre-wrap keeps the \t as a visible tab segment;
// each tab advances to the next 8-space stop at layout time.
const ledger =
  "lamplight\tone hour\n" +
  "walnuts\ta handful\n" +
  "silence\tseven minutes";

const font = "400 16px 'JetBrains Mono', ui-monospace, monospace";
const handle = prepare(ledger, font, { whiteSpace: 'pre-wrap' });

// layout() returns the fit height; the browser then paints the same tabs
// using its own tab-size: 8 so the two agree pixel-for-pixel.
const { height, lineCount } = layout(handle, 440, 16 * 1.7);