v1.0

Typography

One type system, seven token families, applied with a single utility class.

Create UI ships a single, unified type system. Every text style is a token, and each token is applied with one text-{token} utility class that carries its font size, line height, letter spacing, and weight together. You name the role the text plays (text-heading-h1, text-paragraph-md, text-numeric-xl) instead of tuning four separate properties by hand.

There are 37 of them across seven families. Every token knows its metrics at every breakpoint, so a class like text-heading-h1 resolves to a different size on a phone, a tablet, and a desktop, while the families that are deliberately fixed resolve to the same value everywhere:

Responsive · 3 values
Mobile320 to 767px
Aa
36px
Tablet768 to 1279px
Aa
48px
Desktop1280px and up
Aa
56px

One class, three values. The outlined panel is your screen right now, so resize the window and watch it move.

2 media queries in the system, 0 in your markup

Why one class

The same heading, written two ways:

// Raw values: four coupled properties, hand-managed at every breakpoint.
<h1 className="text-[36px] leading-[1.2] font-medium tracking-[-1.4px] md:text-[48px] md:tracking-[-1.6px] xl:text-[56px] xl:tracking-[-1.8px]">
  Ship faster with Create UI
</h1>
 
// Semantic token: name the role, every metric and breakpoint is baked in.
<h1 className="text-heading-h1">Ship faster with Create UI</h1>

The payoff:

  • One decision, not four. Size, line height, letter spacing, and weight travel together, so they can never drift out of sync on a stray element.
  • Responsive for free. The three breakpoints live inside the token, not in your markup.
  • One place to change. Retuning the scale happens in a single data file, not a search-and-replace across every text-[…] in the app.

You never pair a token with leading-* or tracking-*. The class resolves size, line height, and letter spacing from the token itself, and weight from its category, so those four can never drift apart.

Three token groups ask for one companion class alongside the size token: numeric and code take font-numeric for the mono face, and ui-overline-* takes uppercase. The token reference prints the companion on every row that needs one.

Anatomy of a token

Token names follow a category-size shape:

  • heading-h1 is category heading, size h1
  • numeric-xl is category numeric, size xl
  • ui-overline-sm is category ui / overline, size sm

The category decides the default weight and which companion class the token expects. The size decides the metrics. Pick a token and you have picked the right weight and the right scale step in one move.

Usage

Apply a token class to any element. The class sets the four metrics; the companion class, where a family needs one, sets the face or the casing.

<h1 className="text-heading-h1">Ship faster with Create UI</h1>
<p className="text-paragraph-md">
  A unified type system keeps every screen in rhythm.
</p>
<span className="text-numeric-xl font-numeric">$4,299</span>

Tokens compose. A real section is just a few of them stacked, each one naming its role and pairing with a color token:

<article>
  <span className="text-ui-overline-sm text-placeholder uppercase">
    Pricing
  </span>
  <h2 className="text-heading-h2 text-strongest">Team plan</h2>
  <p className="text-paragraph-md text-body">
    Everything your whole team needs to ship a consistent product, billed once
    per seat.
  </p>
  <span className="text-numeric-xl font-numeric text-strongest">$4,299</span>
</article>

That snippet rendered, with each token doing its job in both themes:

Light
Pricing

Team plan

Everything your whole team needs to ship a consistent product, billed once per seat.

$4,299
Dark
Pricing

Team plan

Everything your whole team needs to ship a consistent product, billed once per seat.

$4,299

Three font variables back the families. In a Next.js project createui init declares them with next/font in your root layout; elsewhere it imports the equivalent Fontsource packages. --font-display and --font-body are both Geist, and the page default, so most tokens need no face class at all. --font-numeric is Geist Mono, which gives prices, stats, and code a consistent tabular width, and is what font-numeric selects.

Responsive scale

Token values are defined per breakpoint as [mobile, tablet, desktop]. The generated CSS sets the desktop value on :root and overrides it with two max-width media queries.

Mobile320 to 767px
Tablet768 to 1279px
Desktop1280px and up

Large type scales down on smaller screens, while interface, numeric, and code styles stay fixed so controls and data never shift size:

  • Display and heading scale their font size and letter spacing. Line height is a percentage, so it tracks the font size automatically at every breakpoint.
  • Paragraph and the larger body steps keep their font size fixed and open up line height slightly on smaller screens for readability. The two smallest body steps are fixed outright.
  • ui, numeric, and code are fixed at every breakpoint, so controls and data never shift size.

Token reference

Every token in the system, rendered at its real metrics. The sample sets on the left; the spec rail on the right prints size, then line height, then letter spacing, each value behind a phone, tablet, or desktop mark so the whole curve reads without decoding an arrow. A single number in place of the three marks means that metric is fixed at every breakpoint, which is why most rows carry a mix. The family's weight and face sit in the card header, and clicking a token name copies its full utility class.

displayMarketing hero type, the largest statementsGeist500
Create UI
Desktop96px120%-2pxTablet80px120%-1.8pxMobile64px120%-1.6px
Create UI
Desktop72px120%-1.8pxTablet60px120%-1.6pxMobile52px120%-1.4px
headingPage and section titlesGeist500
Design at scale
Desktop56px120%-1.8pxTablet48px120%-1.6pxMobile36px120%-1.4px
Design at scale
Desktop48px130%-1.6pxTablet36px130%-1.4pxMobile32px130%-1.2px
Design at scale
Desktop36px130%-1.4pxTablet32px130%-1.2pxMobile24px130%-0.8px
Design at scale
Desktop32px140%-1.2pxTablet24px140%-0.8pxMobile20px140%-0.6px
Design at scale
Desktop24px140%-0.8pxTablet20px140%-0.6pxMobile18px140%-0.4px
Design at scale
Desktop20px150%-0.6pxTablet18px150%-0.4pxMobile16px150%-0.2px
bodyDense, functional UI copyGeist500
Dense, functional copy for product interfaces. It sets tight and stays compact when a label runs onto a second line.
Desktop20px28px-0.5pxTablet20px28px-0.5pxMobile20px30px-0.5px
Dense, functional copy for product interfaces. It sets tight and stays compact when a label runs onto a second line.
Desktop18px26px0pxTablet18px26px0pxMobile18px28px0px
Dense, functional copy for product interfaces. It sets tight and stays compact when a label runs onto a second line.
Desktop16px24px0pxTablet16px24px0pxMobile16px26px0px
Dense, functional copy for product interfaces. It sets tight and stays compact when a label runs onto a second line.
Every breakpoint14px20px0px
Dense, functional copy for product interfaces. It sets tight and stays compact when a label runs onto a second line.
Every breakpoint12px18px0px
paragraphLong-form reading, generous line heightGeist400
Long-form reading opens up its line height, so a block of running text stays comfortable across several lines instead of feeling cramped the way dense interface copy does.
Desktop24px34px-0.6pxTablet24px36px-0.6pxMobile24px38px-0.6px
Long-form reading opens up its line height, so a block of running text stays comfortable across several lines instead of feeling cramped the way dense interface copy does.
Desktop20px30px-0.2pxTablet20px32px-0.2pxMobile20px34px-0.2px
Long-form reading opens up its line height, so a block of running text stays comfortable across several lines instead of feeling cramped the way dense interface copy does.
Desktop18px28px0pxTablet18px30px0pxMobile18px32px0px
Long-form reading opens up its line height, so a block of running text stays comfortable across several lines instead of feeling cramped the way dense interface copy does.
Desktop16px26px0pxTablet16px28px0pxMobile16px28px0px
Long-form reading opens up its line height, so a block of running text stays comfortable across several lines instead of feeling cramped the way dense interface copy does.
Desktop14px22px0pxTablet14px24px0pxMobile14px24px0px
uiButtons, inputs, badges, tabs, navigationGeist500
Fixed · every breakpoint
control
Action label
Every breakpoint18px24px-0.5px
Action label
Every breakpoint16px22px-0.5px
Action label
Every breakpoint14px20px-0.3px
Action label
Every breakpoint12px16px-0.2px
Action label
Every breakpoint11px12px-0.1px
overline
Section
Every breakpoint14px20px1px
+ uppercase
Section
Every breakpoint12px18px1px
+ uppercase
Section
Every breakpoint11px16px1px
+ uppercase
caption
Helper text explaining the field
Every breakpoint14px20px0px
Helper text explaining the field
Every breakpoint11px16px0px
numericPrices, stats, counters, tabular figuresGeist Mono500
Fixed · every breakpoint
1,284$4,29999.9%
Every breakpoint32px36px-1px
+ font-numeric
1,284$4,29999.9%
Every breakpoint24px28px0px
+ font-numeric
1,284$4,29999.9%
Every breakpoint18px22px1px
+ font-numeric
1,284$4,29999.9%
Every breakpoint14px18px1px
+ font-numeric
1,284$4,29999.9%
Every breakpoint12px16px1px
+ font-numeric
codeInline code and code previewsGeist Mono500
Fixed · every breakpoint
createUI({ theme: 'indigo' })
Every breakpoint24px150%0px
+ font-numeric
createUI({ theme: 'indigo' })
Every breakpoint18px150%0px
+ font-numeric
createUI({ theme: 'indigo' })
Every breakpoint14px150%0px
+ font-numeric
createUI({ theme: 'indigo' })
Every breakpoint12px150%0px
+ font-numeric
  • Use heading and display for titles, body for UI copy, paragraph for long-form reading, ui for controls, numeric for figures, code for code.
  • Pair a type token with a color token (text-strongest, text-body, text-placeholder) rather than baking color into the type choice.

How it works

You do not need this to use the tokens, but it helps to know the numbers are generated, not hand-written.

A token flows through three layers before it reaches your markup.

1. Token data. The source of truth. Each token is defined as plain data: a category plus its responsive metrics, written [mobile, tablet, desktop].

lib/createui-typography.ts
"heading-h1": {
  fontSize: r(36, 48, 56),
  lineHeight: pct(120),
  letterSpacing: r(-1.4, -1.6, -1.8),
},

The category (heading) supplies the font family and default weight; the size step (h1) supplies the metrics.

2. CSS custom properties. buildTypographyCssVars() in registry/config.ts turns each token into its four values, and the CSS writer expands them into four --text-* variables on :root, overriding the responsive ones in two max-width media queries. Those two blocks are the only @media rules in the whole stylesheet:

styles/globals.css
:root {
  --text-heading-h1: 56px; /* desktop, ≥ 1280px */
  --text-heading-h1--line-height: 120%;
  --text-heading-h1--letter-spacing: -1.8px;
  --text-heading-h1--font-weight: 500;
}
 
@media (max-width: 1279px) {
  :root {
    --text-heading-h1: 48px; /* tablet */
    --text-heading-h1--letter-spacing: -1.6px;
  }
}
@media (max-width: 767px) {
  :root {
    --text-heading-h1: 36px; /* mobile */
    --text-heading-h1--letter-spacing: -1.4px;
  }
}

3. Tailwind utilities. Those variables are registered in @theme inline, so Tailwind v4 generates the text-* utility from them, and the class already knows its four metrics at every breakpoint.

When a project runs createui init, the CLI emits the same variables into the consumer's stylesheet, so the type scale travels with the components.

Every one of these tokens ships with the components.Install Create UI