X Data

Star on GitHubFeedback
X DataNative HTMLot-prose

Typography

Long text, set to be read: it styles plain HTML by element, and punctuation hangs in the margin.

Report an issue with Typography

Example

Why two faces are enough

Every extra typeface is another voice in the room. Two is a conversation.

The grotesque carries everything the interface says: labels, buttons, the names of things. The italic serif carries everything you say back. Nothing else is needed to tell the two apart, and nothing else is added. In the stylesheet, the whole rule is one line: .ot-yours sets the expression, in italic. Read the full contract.

The space isn’t empty. It’s waiting for the reader.

From the studio’s notes, 2026

What this rules out

  • Icons that need a legend.
  • A third face for “personality”.
  • Colour that means nothing.

What it asks of us

  1. Write every label so it can stand alone.
  2. Set the hierarchy with size and weight first.
  3. Leave the space empty when in doubt.

In the stylesheet

.ot-yours {
  font-family: var(--ot-expression);
  font-style: italic;
}
TokenSetsFigure
--ot-hairlineRules1px
--ot-strokeEmphasis lines2px
book

On leaving space

A page is mostly paper. The type takes a small part of it, and the rest is the room the reader breathes in. Set the margins first, before a single word, and the words will know where to stand.

The old compositors had a rule for it: indent or space, never both. An ordered page needs little else, only the steady measure of its lines and the space kept between them.

What is left out is part of the design. It is the part the reader never notices, and the part they would miss.


Two faces, one accent, and a great deal of paper: that is the whole of the system.

run-on

On leaving space

A page is mostly paper. The type takes a small part of it, and the rest is the room the reader breathes in. Set the margins first, before a single word, and the words will know where to stand.

The old compositors had a rule for it: indent or space, never both. An ordered page needs little else, only the steady measure of its lines and the space kept between them.

What is left out is part of the design. It is the part the reader never notices, and the part they would miss.


Two faces, one accent, and a great deal of paper: that is the whole of the system.

swiss

A page is mostly paper, and the type takes a small part of it.

Set the margins first, and the words will know where to stand.

book

A page is mostly paper, and the type takes a small part of it.

Set the margins first, and the words will know where to stand.

run-on

A page is mostly paper, and the type takes a small part of it.

Set the margins first, and the words will know where to stand.

Install

Run it in a project that has the shadcn CLI set up. It brings the 0nlyType base along.

npx shadcn@latest add https://thedirectors.agency/ui/r/typography.json

Usage

typography-example.tsx
import { Prose, ProseLead, type ProseProps } from "@/components/ui/typography"function Passage({ variant }: { variant: ProseProps["variant"] }) {return (<Prose variant={variant}><h3>On leaving space</h3><p>A page is mostly paper. The type takes a small part of it, and the rest is the room the reader breathes in. Setthe margins first, before a single word, and the words will know where to stand.</p><p>The old compositors had a rule for it: indent or space, never both. An ordered page needs little else, only thesteady measure of its lines and the space kept between them.</p><p>What is left out is part of the design. It is the part the reader never notices, and the part they would miss.</p><hr /><p>Two faces, one accent, and a great deal of paper: that is the whole of the system.</p></Prose>)}export default function Example() {return (<div className="grid gap-y-16"><Swiss />{(["book", "run-on"] as const).map((v) => (<div key={v} className="grid gap-y-4"><span className="ot-label">{v}</span><Passage variant={v} /></div>))}</div>)}

Contract

  • Underneath: native.
  • Anatomy: <article class="ot-prose"> wrapping plain HTML: headings, p, .ot-prose-lead, blockquote, lists, code.
  • Text-size em, i and block quotations use the expression reading grade from base. Nested italics do not multiply the scale again. Italics inside display headings keep their display settings.
  • swiss (the default, data-variant="swiss"): set for reading at --ot-measure. Headings step by the dynamics; quotes are in the expression; quote marks and list dashes hang in the margin so the edge stays true (hanging-punctuation where supported); numbered lists step in instead, a number being too wide to hang, so the numbers stand on the edge. A nested list steps in by its dash, so its dash hangs under its parent's words, and list items take half a paragraph's pause (0.5em) between them. Code is the voice, condensed.
  • book: the page of a book, by the compositor's rule "indent or space, never both" (Tschichold's Penguin rules, Bringhurst). Paragraphs follow on with no space between them, each indented an em and a half, and the first after a heading or a break flush; they are justified and hyphenated, with old-style figures where the face has them. The opening line after a heading is set in spaced capitals (::first-line, so it re-sets itself at every width). A break (hr) is a dinkus of three spaced dots instead of a rule. Quotes are indented both sides at body size.
  • run-on: the scribe's paragraph. A passage's paragraphs run on as one block, and where each begins a pilcrow (¶, the expression face, ink, decorative to screen readers) stands in the line: the silence between paragraphs is kept as one mark. The lead and anything that isn't a paragraph stay blocks. For short passages; long reading wants swiss or book.

Props

Props of Typography
PropTypeDefaultWhat it does
asChildbooleanfalseSet the child element instead of rendering an article.
ProseLeadp propsAn opening paragraph at the lead size, in ink.
variant"swiss" | "book" | "run-on""swiss"swiss: space between paragraphs and punctuation hung in the margin. book: a book's page; paragraphs follow on without a space, each indented, justified and hyphenated, the opening line in spaced capitals, old-style figures, and a break is three dots instead of a rule. run-on: a passage's paragraphs run on as one block, a pilcrow standing where each begins.

Craft

Where its move comes from

Where its move comes from
ComponentReferenceMove
ProseSwiss typesettingHanging punctuation keeps the edge true
Book proseThe book page: "indent or space, never both"Paragraphs indented, not spaced; the opening line in capitals; a dinkus for a break
Run-on proseThe scribe's pilcrowParagraphs run on as one block, a ¶ where each begins