Typography

Two type families doing distinct jobs — Geist for nearly everything, Sharp Grotesk for the page title — plus Geist Mono for true technical values. Set type with the standard text-* / font-* utilities, never inline px. This page maps every utility to its rendered value, explains the rules, and shows them in context.

Principles

Two jobs, sparingly applied

The system leans on Geist and earns hierarchy from size, weight and colour — not from reaching for a different typeface. Two rules carry most of the weight:

Mono is for machines, not labels

A monospace face is only doing a job when characters must align in a grid or be read one-by-one — IDs, API keys, hashes, versions, code, shortcuts. On an eyebrow or a header it adds no function, only a borrowed “developer-tool” signal, and it reads slower. So uppercase labels stay in Geist; mono is reserved for technical values.

The display face is for the page title

Every mature product UI (Carbon’s “productive” set, Stripe, Linear, Vercel) keeps one neutral family for the app and walls the expressive face off to marketing. Sharp Grotesk carries the page / hero title; section headers, card titles, metric numbers and pills are Geist. Numbers use tabular-nums to align — never a special display face.

Families

font-* utilities

Geist is the default and does almost all the work; Sharp Grotesk (the display cuts) carries the page title; Geist Mono is for technical values only.

font-default

Geist

next/font (Google)

The default for almost everything — body, UI, buttons, table data, metric numbers, AND every uppercase label/eyebrow. Hierarchy comes from size, weight and colour.

Manage campaigns across Meta, Google, and LinkedIn.
font-display

Sharp Grotesk DB Semibold · width 21

self-hosted (licensed)

The page title ONLY — one display heading per view. Section headers, card titles and metric values are Geist (see the rules below). The wider width-25 cut (font-display-wide) is marketing-only and isn't used in the app.

Top campaigns this month
font-mono

Geist Mono

next/font (Google)

Technical values only — IDs, API keys, hashes, version strings, code, keyboard shortcuts. Things you'd copy-paste or read character-by-character. NOT eyebrows or labels.

ak_live_3f9c2a · ⌘K · v2.4.0
font-sticker

Sharp Grotesk DB Semibold · width 15

self-hosted (licensed)

Expressive brand badge / pill labels only (the Badge brand tones).

Learning

Scale

The app type scale — 6 steps

Design with this closed set of seven role-based steps, not the raw ramp. The app lives almost entirely on Caption (12px) and Body (14px); everything above is a deliberate, role-mapped step. The full migration to semantic role tokens (text-caption …) is tracked in GOATS-415; today each role maps onto the existing text-* utility shown.

Caption 12px

text-xs

Captions, eyebrows, table headers, KPI labels, chips

The quick brown fox

Body 14px

text-sm

Default UI text — body, buttons, inputs, table cells

The quick brown fox

Body large 16px

text-base

Lead paragraphs, card body, emphasized body

The quick brown fox

Title 18px

text-lg

Card / list-item titles (h3) and section headers (h2 = + weight + eyebrow)

The quick brown fox

Page 24px

text-2xl

Page titles (h1)

The quick brown fox

Display 30px

text-3xl

Hero metric / big stat — the one big step

The quick brown fox

Tokens

Underlying text-* utilities

The raw Tailwind tokens the roles above map onto, at our 100% (16px) base. Sizes are rem-based, so they scale together if the base changes. There is no text-md — the 16px step is text-base. Only the six in-scale sizes are shown: text-2xs (10px, below the 12px floor), text-xl (20px, collapsed into Title) and the marketing ramp (text-4xltext-7xl, 36–72px) are not part of the app scale.

text-xs

0.75rem · 12px

lh 16px

The quick brown fox
text-sm

0.875rem · 14px

lh 20px

The quick brown fox
text-base

1rem · 16px

lh 24px

The quick brown fox
text-lg

1.125rem · 18px

lh 28px

The quick brown fox
text-2xl

1.5rem · 24px

lh 32px

The quick brown fox
text-3xl

1.875rem · 30px

lh 36px

The quick brown fox

Weight

font-* weight utilities

Geist carries a real 400–700 range — lean on it for emphasis (bold for metrics and key values, regular elsewhere). The Sharp Grotesk display family ships a single Semibold cut declared across 500–800, so every weight utility renders the same glyph: on display type, vary tone, not weight.

Geist (font-default)

Adsidefont-normal · 400
Adsidefont-medium · 500
Adsidefont-semibold · 600
Adsidefont-bold · 700

Sharp Grotesk (font-display)

Adsidefont-normal · 400
Adsidefont-medium · 500
Adsidefont-semibold · 600
Adsidefont-bold · 700

Guide

How to use it — role recipes

Reach for these combinations rather than assembling sizes by hand. Each row is a live render of the exact class string beside it; copy it verbatim. Headings h1h3 also pick up -0.02em tracking from the base layer automatically.

Page title

The ONE display-face heading on a page. Use PageHeader, which bakes this in.

font-display text-2xl font-semibold text-content-emphasis
Top campaigns

Section header (H2)

Groups within a page. Geist — the display face stops at the page title.

font-default text-lg font-semibold text-content-emphasis
By spend this month

Card title (H3)

Title inside a card / list row. Geist, semibold for weight-based emphasis.

font-default text-sm font-semibold text-content-emphasis
Summer Sale 2026

Metric value

Big KPI numbers (the Display step, 30px). Geist (not the display face) + tabular-nums so digits align; bold carries the emphasis.

font-default text-3xl font-bold tabular-nums tracking-[-0.02em] text-content-emphasis
$12,450

Lead / large body

Intro paragraphs and emphasis copy (the Title step at regular weight; 20px is retired).

font-default text-lg text-content-default
Your AI head of paid, working across every channel.

Body

Default reading text (brand Body 2 · 16px).

font-default text-base text-content-default
Subscribe to keep access to your clients.

Dense UI text

The app workhorse (brand Body 3 · 14px) — buttons, inputs, table cells, sidebar, compact UI.

font-default text-sm text-content-default
Summer Sale 2026

Caption / helper

Secondary helper text, timestamps, sub-labels.

font-default text-xs text-content-subtle
Updated 2 minutes ago

Uppercase label / eyebrow

KPI captions, overlines, section eyebrows. Geist — NOT mono. Case + tracking + tone do the work. Use the Eyebrow component. (Table headers are sentence case — see below.)

font-default text-xs font-medium uppercase tracking-wider text-content-subtle
CAMPAIGN

Technical value (mono)

The ONLY place mono belongs: IDs, API keys, hashes, versions, shortcuts, code — values read character-by-character.

font-mono text-xs text-content-default
ak_live_3f9c2a

Badge sticker

Expressive pill labels (Badge component).

font-sticker text-xs font-semibold
Learning

Demo

What it looks like in the app

A composed slice of a dashboard, using the recipes above. One display heading (the page title); everything else — eyebrows, metric numbers, card titles, the table, the rank pill, the buttons — is Geist, with mono kept to the one technical value (the ad ID).

Overview

Top creatives

Spend

$12,450

Impressions

1,284,902

CTR

3.18%

Performance breakdown

CreativeSpendCTRROAS
Summer Sale 2026$4,2103.9%4.1×
Back to School$3,1802.7%3.4×
Evergreen — UGC$2,0603.1%2.9×
#1

Summer Sale 2026

ad_120214… · 23877291

Rules

Do & don't

  • Do
  • Use semantic size/family utilities (text-sm, font-display) — never inline style px.
  • Keep the display face (font-display) for the page / hero title only; section and card titles are font-default.
  • Set numbers and metrics in Geist with tabular-nums; reach for font-bold to make a value read as important.
  • Set uppercase labels in font-default with tracking-wider (the Eyebrow component).
  • Sentence case in prose and headings.
  • Don't
  • Use font-mono for eyebrows, headers or labels — mono is for IDs, keys, hashes, versions, code and shortcuts only.
  • Put section headers, card titles, metric numbers or pills in the display face — that re-imports the marketing ramp into dense UI.
  • Reach for text-md — it doesn't exist; the 16px step is text-base.
  • Expect font-bold to look heavier than font-semibold on display type — same single cut.
  • Use pure black; ink is the content-emphasis token.