Agent docs

12 · Typography

The ramp, the roles, the title sets, the ink ladder, mono, tracking, truncation, and the do-nots.

The ramp, the roles, the title sets, the ink ladder, mono, tracking, truncation, and the do-nots. Sources: kit theme/index.css (fonts and base size), the R6 survey of every class the four codebases use (.claude/output/20260816-r1-sitting-1/R6-typography.md, exact counts), the structure note docs/plan/60-typography-structure.md, and the owner's rulings in docs/plan/57-sitting-1-answers.md (D9a, D10a) and docs/plan/59-showcase-feedback-4.md (D3, D4, G1, V1). The rendered companions are the ui page /components/typography (every role and variant on fixture data) and the pattern page /patterns/typography-composites (where two or more roles meet). This doc says why and what not to do, and never repeats what they show.

Audience: an agent (or person) choosing type for a screen, or reviewing one.

§A Principles and mental models#

1. The ramp is three working sizes and two display sizes. text-xs, text-sm and text-base carry the apps (R6: 111/103/233 in speedway, 162/143/155 in walmart). lg and up appear a handful of times per app, and headings come from PageTitle and Card rather than raw classes. 2xl and 3xl are display sizes for the playground landing and nothing else. A fourth working size is a design decision made in this doc, never a class typed on a page. Never type an arbitrary pixel size (text-[13px]) without a reason written beside it; R6 counted 84 across the tree, 41 in walmart, and each is a size outside the ramp.

2. Roles, not sizes. Reach for the component that owns the role before the class that approximates it: PageTitle for the page name, Card title and subtitle for a block, Kicker for a group title or a tile label, StatTile for a headline number, Chip for a token, a Button for an action. The ui page's roles table renders each with the classes it applies, so a hand-rolled copy can be spotted by its class string.

3. Mono is for identifiers, never decoration. Doc 1 §A already says it (01-foundations.md:28). This doc adds the four clauses (§B, "Mono in place"). Inside a button, a count or an id keeps the button's size and inherits its colour with no separate opacity. Inside a code block, mono is the medium and the header title is mono only when it is itself code. Inside an alert, an identifier is the inline code chip, never a mono paragraph, and the alert title is never mono. Inside a chip, the label is sans and only an id, SKU or path is mono. Never use mono to make text look technical.

4. One kicker idiom, one component, never re-typed. Doc 9 §A5 names the idiom the house section-opener. The kit now ships it as Kicker (packages/ui/docs/kicker.md) in two variants, default (11px, 0.08em, the group title and the eyebrow) and bricked (12px, tracking-wide, the tile label read inside its own frame). The one place the idiom drops to weight 500 is a railed sidebar's group kicker (navGrouping="railed", doc 08 §A10), so a group title recedes behind its rows without leaving the /65 ink floor of §A9; the default flat sidebar keeps 600. Never type uppercase tracking-wide text-xs on a page again; R6 counted about 76 hand-typed copies and 176 uppercase classes in walmart alone, which is how the same label ended up in three recipes on one landing page (doc 59 V1).

5. Four title levels and a label. A page never invents a fifth. Page title (PageTitle, h1 text-2xl font-bold), display title (the landing's h2 only), section or card title (Card title card-title text-base with its text-sm subtitle, titleDivider when the body is a table), group title (Kicker default) and the tile label (Kicker bricked). The stat label belongs to StatTile and is sentence case. A card's title node may carry a leading icon at size 18 in the primary tone. The icon never sits in a row of its own above the title. Never place a section's action under its title in a row of its own. It lives in the toolbar slot (doc 2 §A10).

6. Weight is hierarchy, not emphasis. Bold in a sentence is a smell. The ramp's weights (400 body, 500 labels, 600 kickers and card titles, 700 page and display titles) already say what is above what. Never bold a phrase inside body text to make it louder.

7. Case is meaning. Uppercase belongs to the kicker and to nothing else. Sentence case everywhere else, including stat labels and buttons; table headers render as authored, in Title Case, per doc 06 §B (06-tables.md:39, owner rounds 2026-07-10 and 07-16). Never uppercase a button, a table header or a status pill.

8. Colour on text is meaning, and the meaning comes from doc 3. A tone on text says status or intent (the five semantic tones, docs/design-language/03-status-language.md), never emphasis or decoration. The ui page shows tone-on-text once and points here. A kicker never carries a tone (a coloured label reads as a status). Measured 2026-08-17: in the light theme every one of the five tones on 14px body text sits under AA against a card (warning 2.87, error 3.44, info 3.61, success 3.90, primary 4.20) and all five pass in dark, so tone on text is safe at 16px and up, or where the tone rides a pill or chip that brings its own surface; below that in light it is a token-level call the owner has not made yet. Never colour a paragraph or a title for effect.

9. Gray is hierarchy, and it is a ladder, not a slider. text-base-content at full ink is titles and values. /70 body prose and the footer. /65 card subtitles, stat labels, kickers, tile labels, hints and help text. /45 a count riding inside a control (it inherits the control's ink, so it is never the only text on a surface). /30 a disabled icon wash. Two rungs the ladder does NOT have for small text: /50 and /60, which measure 3.2 to 4.4 against the light theme's page at 11 to 12px (the wave 2b categorical pass, both themes) and fail AA; they were the kicker, hint and footer inks until 2026-08-17. Never invent /55 because it "looked right", and never drop small text below /65. The ui page renders the ladder as a column with each rung's role.

10. Tracking lives on the kicker and nowhere else. 0.08em on the default kicker and tracking-wide on the bricked one are the only letter-spacing in the system. Body, titles and buttons keep the font's own spacing. Spacing around type comes from the containers (doc 9 §A5's 30/26px section rhythm, Card's padding, the title stack's gap-2), never from margins typed onto a text node.

11. Truncated text always has a way to read the whole value. truncate pairs with a Tooltip carrying the full text (packages/ui/docs/tooltip.md, "When to reach for it"). line-clamp is for prose that must stop. break-all is for paths (the Markdown inline-code rule). Never truncate a value the reader must be able to read in full without giving them the tooltip.

12. A button never underlines. At rest or on hover, in any variant. Hover feedback is the shade wash (packages/ui/src/button/button.tsx:154-157). Underline belongs to prose links inside Markdown. A project may rule otherwise for its own inline links, and then it does so everywhere. Never mix the two on one surface (doc 59 D4).

§B Presets#

Each row is the type recipe for a role, and the component that produces it. Use the component. The classes are printed so a hand-rolled copy is recognisable in review.

RoleComponentClasses todayNotes
Page titlePageTitle (packages/ui/src/page-title/page-title.tsx:86)h1 text-2xl font-bold, truncatingone per page, first in the composition order (doc 9 §A4)
Display titlethe landing's LandingSection h2 (apps/playground/src/app/landing-sections.tsx:58)text-2xl md:text-3xl font-bold tracking-tight under a Kickerlanding only
Card or section titleCard (packages/ui/src/card.tsx:114)h2 card-title text-base; subtitle text-base-content/65 text-smheader doubles as documentation (doc 5 §A1); titleDivider for a table body
Group titleKicker defaulttext-[11px] font-semibold tracking-[0.08em] uppercase text-base-content/65the level below a card title; as="h3" for the outline
Tile labelKicker variant="bricked"text-xs font-semibold tracking-wide uppercase text-base-content/65a demo or specimen tile's own name; not the Button sense of bricked
Stat label and valueStatTile (packages/ui/src/stat-tile/stat-tile.tsx:78,85)label text-xs font-semibold text-base-content/65, value font-mono text-3xl font-semibold tabular-numsthe value's tone is the tile's tone
Bodyprose in a Card or Markdowntext-base text-base-content/70 (docs bumped one size, doc 53 G6.9)max-w-2xl for reading measure on a landing
Help text and hintsunder a field, a footer, a hint linetext-xs text-base-content/65never smaller than xs, never lighter than /65
Mono in placea count or id in a Button, a CodeBlock title that is code, an inline code chip in an Alert, a Chip valuefont-mono, inheriting size and colour from the hostsee §A3; the See all count on the landing is the reference case
Table header and cellTableheader as authored (Title Case, doc 06 §B) at text-xs font-medium text-base-content/65 (packages/ui/src/table/table.tsx:157); cells text-sm, ids mono, numerals right-aligned tabular-numsthe pattern page shows the pair
Message textAlert title over body, toast one-liner, EmptyState title over hinttitle text-sm font-semibold, body text-sm text-base-content/70, hint text-xs text-base-content/60one size step between title and body, never two

§C Use cases through the apps#

CaseWhereWhat it shows
The eyebrow above a landing sectionapps/playground/src/app/landing-sections.tsx (Kicker over the display h2, purpose line under)the title stack at display scale, gap-2 inside it
A demo tile with a bricked labelapps/playground/src/app/landing-strip.tsx (Tile)a Card with no title, the label as the tile's name, the component link in the toolbar position
Group titles inside one card/components/kicker, "Group titles inside one card"Kicker as="h3" opening two form groups under one card title
A count riding a text buttonthe landing's "See all 41 pages"mono, the button's size, the button's colour, no separate opacity (doc 59 C3)
The gray ladder in one screen/components/typography, "The ink ladder"each rung labelled with its role
Composites/patterns/typography-compositestitle stack, label and value pairs, table header versus cell, identity row, message text, prose in a card, the four levels in situ
Speedway's five kicker namesspeedway app.css (.section-label, .panel-kicker, .rail-label, .field-label, .orch-k), doc 9 §A5the idiom before the primitive; the app adopts Kicker on its next deliberate kit upgrade
Walmart's hand-typed uppercasewalmart-mvp/frontend/src/pages/Admin.tsx, NewCatalog.tsx, WalmartExplorer.tsx, Upload.tsx, WalmartErrors.tsx (R6 survey)176 uppercase and 198 tracking- classes for one idiom; the reason §A4 exists
@versable-git/ui · reference, canon, and method, read in place