Skip to main content

Typography

Typography tokens define the text system of ttoss: font families, weights, size ramps, line-height, letter-spacing, and semantic text styles.

Typography must be:

  • Readable — supports long-form content, microcopy, and scanning.
  • Hierarchical — communicates importance and structure without visual noise.
  • Robust — survives 200% zoom, user text-spacing overrides, and locale/script differences.
  • Natively responsive — responsiveness lives in the token engine, not in component code.
  • Small and predictable — avoids token-per-component drift.

Key principle: typography is not a component. It is a global contract that components consume.

This system is built on two explicit layers:

  1. Core Tokens — intent-free primitives
  2. Semantic Tokens — stable text style contracts consumed by UI code

Rule: Core typography is never referenced in components. Components must always consume semantic typography.

Scope: Tokens vs Text Components vs HTML

Typography tokens define styles. They do not define document structure.

  • HTML semantics (h1…h6, p, label, etc.) express meaning and accessibility.
  • Text components (e.g., Text, Heading) are implementation APIs that choose:
    • the HTML element (as="h2") for semantics
    • the semantic token (style="text.title.md") for appearance
  • Design tokens define the style contracts (text.title.md) and the primitives they reference.

Tag choice (h2) and style choice (text.title.md) are intentionally decoupled.


Core Tokens

Core tokens are intent-free primitives and the single source of truth for responsiveness.

They exist to:

  • centralize fluid logic (all clamp() formulas live here)
  • keep semantic tokens as aliases (stable names, theme-tunable engine)
  • make the system predictable (no ad-hoc coefficients in semantics)

Output Guidance (Web)

The following is specific to CSS/web output and does not affect the semantic contract.

  1. A query container rule for layout surfaces (recommended):
.tt-container {
container-type: inline-size;
}
  1. A robust fallback strategy in build output:
  • emit a viewport-safe fallback first
  • override with container units when supported
:root {
--tt-type-ramp-text-3: clamp(16px, calc(0.8vi + 12px), 18px);
--tt-type-ramp-display-3: clamp(28px, calc(1.6vi + 20px), 40px);
}

@supports (width: 1cqi) {
:root {
--tt-type-ramp-text-3: clamp(16px, calc(0.8cqi + 12px), 18px);
--tt-type-ramp-display-3: clamp(28px, calc(1.6cqi + 20px), 40px);
}
}
  1. The core token set below (ramps + primitives).

Core Token Set

Core typography is composed of two groups:

  1. Font primitives — fundamental typographic properties.
  2. Size ramps — the responsive engine that controls typographic scale.

Font Primitives

CategoryTokensNotes
Font familiescore.font.family.sans, core.font.family.serif (optional), core.font.family.monoDefines the font stacks used across the system.
Font weightscore.font.weight.regular (400), core.font.weight.medium (500), core.font.weight.semibold (600), core.font.weight.bold (700)If using variable fonts, weights may be tuned at the theme level. Components still consume semantic styles only.
Leading (line-height)core.font.leading.tight, core.font.leading.snug, core.font.leading.normal, core.font.leading.relaxedUnitless multipliers for scalable line-height.
Tracking (letter-spacing)core.font.tracking.tight, core.font.tracking.normal, core.font.tracking.widetight may be used for large headings. wide is intended for short labels; avoid for body text.
Optical sizing (optional)core.font.opticalSizing.auto, core.font.opticalSizing.noneEnables optical adjustments for variable fonts when supported.
Numeric features (optional)core.font.numeric.proportional, core.font.numeric.tabulartabular is recommended for dashboards or frequently updating numeric values.

Size Ramps (Responsive Engine)

Ramps define the responsive typographic scale.
Each ramp is expressed using clamp(min, preferred, max).

RampTokensPurpose
Text scalecore.type.ramp.text.1..6Body text, labels, and dense UI typography.
Display scalecore.type.ramp.display.1..6Headings, titles, and high-hierarchy display text.

Rule: Semantic typography styles must map to ramp steps.
Semantic tokens never define new clamp() formulas.

Example (Core Typography Definition)

const coreTypography = {
font: {
family: {
sans: 'ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Arial, "Apple Color Emoji", "Segoe UI Emoji"',
mono: 'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace',
},

weight: {
regular: 400,
medium: 500,
semibold: 600,
bold: 700,
},

leading: {
tight: 1.15,
snug: 1.25,
normal: 1.5,
relaxed: 1.7,
},

tracking: {
tight: '-0.02em',
normal: '0em',
wide: '0.04em',
},

opticalSizing: {
auto: 'auto',
none: 'none',
},

numeric: {
proportional: 'proportional-nums',
tabular: 'tabular-nums',
},
},

type: {
/**
* Responsive engine (container-first), theme must emit a viewport-safe fallback first,
* then override these when supported via @supports (width: 1cqi).
*/
ramp: {
text: {
1: 'clamp(12px, calc(0.6cqi + 10px), 14px)',
2: 'clamp(14px, calc(0.7cqi + 11px), 16px)',
3: 'clamp(16px, calc(0.8cqi + 12px), 18px)',
4: 'clamp(18px, calc(0.9cqi + 13px), 20px)',
5: 'clamp(20px, calc(1.0cqi + 14px), 24px)',
6: 'clamp(24px, calc(1.2cqi + 16px), 28px)',
},

display: {
1: 'clamp(20px, calc(1.2cqi + 16px), 28px)',
2: 'clamp(24px, calc(1.4cqi + 18px), 32px)',
3: 'clamp(28px, calc(1.6cqi + 20px), 40px)',
4: 'clamp(32px, calc(1.8cqi + 22px), 48px)',
5: 'clamp(40px, calc(2.2cqi + 26px), 64px)',
6: 'clamp(48px, calc(2.6cqi + 30px), 80px)',
},
},
},
};

Expected consumption pattern: semantic styles reference core tokens by alias (e.g., text.body.md → core.type.ramp.text.3, text.display.lg → core.type.ramp.display.5).


Semantic Tokens

Semantic typography tokens are the only typography API for components.

Token structure

text.{family}.{step}
  • family: display | headline | title | body | label | code
  • step: lg | md | sm (code uses md | sm)

Text Families

Each text family represents a distinct typographic role in the interface.

familyMeaning
displayLarge, high-impact text used for hero sections or prominent page headers. Intended for strong visual hierarchy and limited use.
headlineSection or page headings that structure content and guide scanning through the interface.
titleTitles for surfaces such as cards, panels, dialogs, and structured UI sections.
bodyDefault text for paragraphs, descriptions, and long-form reading. Optimized for readability.
labelShort UI text such as field labels, button text, badges, and metadata.
codeMonospaced text for code snippets, logs, identifiers, or technical data.

Canonical semantic set

  • text.display.{lg|md|sm}
  • text.headline.{lg|md|sm}
  • text.title.{lg|md|sm}
  • text.body.{lg|md|sm}
  • text.label.{lg|md|sm}
  • text.code.{md|sm}

Keep this set stable. Add new families only if they represent a genuinely new typographic function.

Semantic Tokens Summary Table

tokenuse when you are building…contract (must be true)default mapping
text.display.lghero display / landing herostrongest hierarchy; avoid overusecore.type.ramp.display.5, core.font.weight.bold, core.font.leading.tight
text.display.mdlarge display headingshigh hierarchycore.type.ramp.display.4, core.font.weight.bold, core.font.leading.tight
text.display.smsmaller display headingshigh hierarchycore.type.ramp.display.3, core.font.weight.semibold, core.font.leading.tight
text.headline.lgpage/section headlineclear hierarchycore.type.ramp.display.3, core.font.weight.semibold, core.font.leading.snug
text.headline.mdsection headlineclear hierarchycore.type.ramp.display.2, core.font.weight.semibold, core.font.leading.snug
text.headline.smsmall headlinecompact hierarchycore.type.ramp.display.1, core.font.weight.semibold, core.font.leading.snug
text.title.lgsurface titles (cards/modals)title-like, not shoutingcore.type.ramp.text.6, core.font.weight.semibold, core.font.leading.snug
text.title.mddefault surface titletitle-likecore.type.ramp.text.5, core.font.weight.semibold, core.font.leading.snug
text.title.smcompact titlestitle-likecore.type.ramp.text.4, core.font.weight.medium, core.font.leading.snug
text.body.lglong-form body (comfortable)readable; avoid tight leadingcore.type.ramp.text.4, core.font.weight.regular, core.font.leading.normal
text.body.mddefault bodyreadable defaultcore.type.ramp.text.3, core.font.weight.regular, core.font.leading.normal
text.body.smdense body / secondary textstill readablecore.type.ramp.text.2, core.font.weight.regular, core.font.leading.normal
text.label.lgstrong labelsshort text; supports trackingcore.type.ramp.text.3, core.font.weight.medium, core.font.leading.snug
text.label.mddefault labelsshort textcore.type.ramp.text.2, core.font.weight.medium, core.font.leading.snug
text.label.smsmall labels / captionsshort textcore.type.ramp.text.1, core.font.weight.medium, core.font.leading.snug
text.code.mdcode/monospace blocksmono; stable glyph width helpfulcore.type.ramp.text.2, core.font.family.mono, core.font.leading.normal
text.code.sminline code / dense logsmono; compactcore.type.ramp.text.1, core.font.family.mono, core.font.leading.normal

Example

const semanticTypography = {
text: {
display: {
lg: {
fontFamily: '{core.font.family.sans}',
fontSize: '{core.type.ramp.display.5}',
fontWeight: '{core.font.weight.bold}',
lineHeight: '{core.font.leading.tight}',
letterSpacing: '{core.font.tracking.tight}',
fontOpticalSizing: '{core.font.opticalSizing.auto}',
},
md: {
fontFamily: '{core.font.family.sans}',
fontSize: '{core.type.ramp.display.4}',
fontWeight: '{core.font.weight.bold}',
lineHeight: '{core.font.leading.tight}',
letterSpacing: '{core.font.tracking.tight}',
fontOpticalSizing: '{core.font.opticalSizing.auto}',
},
sm: {
fontFamily: '{core.font.family.sans}',
fontSize: '{core.type.ramp.display.3}',
fontWeight: '{core.font.weight.semibold}',
lineHeight: '{core.font.leading.tight}',
letterSpacing: '{core.font.tracking.tight}',
fontOpticalSizing: '{core.font.opticalSizing.auto}',
},
},

headline: {
lg: {
fontFamily: '{core.font.family.sans}',
fontSize: '{core.type.ramp.display.3}',
fontWeight: '{core.font.weight.semibold}',
lineHeight: '{core.font.leading.snug}',
letterSpacing: '{core.font.tracking.normal}',
fontOpticalSizing: '{core.font.opticalSizing.auto}',
},
md: {
fontFamily: '{core.font.family.sans}',
fontSize: '{core.type.ramp.display.2}',
fontWeight: '{core.font.weight.semibold}',
lineHeight: '{core.font.leading.snug}',
letterSpacing: '{core.font.tracking.normal}',
fontOpticalSizing: '{core.font.opticalSizing.auto}',
},
sm: {
fontFamily: '{core.font.family.sans}',
fontSize: '{core.type.ramp.display.1}',
fontWeight: '{core.font.weight.semibold}',
lineHeight: '{core.font.leading.snug}',
letterSpacing: '{core.font.tracking.normal}',
fontOpticalSizing: '{core.font.opticalSizing.auto}',
},
},

title: {
lg: {
fontFamily: '{core.font.family.sans}',
fontSize: '{core.type.ramp.text.6}',
fontWeight: '{core.font.weight.semibold}',
lineHeight: '{core.font.leading.snug}',
letterSpacing: '{core.font.tracking.normal}',
fontOpticalSizing: '{core.font.opticalSizing.auto}',
},
md: {
fontFamily: '{core.font.family.sans}',
fontSize: '{core.type.ramp.text.5}',
fontWeight: '{core.font.weight.semibold}',
lineHeight: '{core.font.leading.snug}',
letterSpacing: '{core.font.tracking.normal}',
fontOpticalSizing: '{core.font.opticalSizing.auto}',
},
sm: {
fontFamily: '{core.font.family.sans}',
fontSize: '{core.type.ramp.text.4}',
fontWeight: '{core.font.weight.medium}',
lineHeight: '{core.font.leading.snug}',
letterSpacing: '{core.font.tracking.normal}',
fontOpticalSizing: '{core.font.opticalSizing.auto}',
},
},

body: {
lg: {
fontFamily: '{core.font.family.sans}',
fontSize: '{core.type.ramp.text.4}',
fontWeight: '{core.font.weight.regular}',
lineHeight: '{core.font.leading.normal}',
letterSpacing: '{core.font.tracking.normal}',
fontOpticalSizing: '{core.font.opticalSizing.auto}',
},
md: {
fontFamily: '{core.font.family.sans}',
fontSize: '{core.type.ramp.text.3}',
fontWeight: '{core.font.weight.regular}',
lineHeight: '{core.font.leading.normal}',
letterSpacing: '{core.font.tracking.normal}',
fontOpticalSizing: '{core.font.opticalSizing.auto}',
},
sm: {
fontFamily: '{core.font.family.sans}',
fontSize: '{core.type.ramp.text.2}',
fontWeight: '{core.font.weight.regular}',
lineHeight: '{core.font.leading.normal}',
letterSpacing: '{core.font.tracking.normal}',
fontOpticalSizing: '{core.font.opticalSizing.auto}',
},
},

label: {
lg: {
fontFamily: '{core.font.family.sans}',
fontSize: '{core.type.ramp.text.3}',
fontWeight: '{core.font.weight.medium}',
lineHeight: '{core.font.leading.snug}',
letterSpacing: '{core.font.tracking.normal}',
fontOpticalSizing: '{core.font.opticalSizing.auto}',
},
md: {
fontFamily: '{core.font.family.sans}',
fontSize: '{core.type.ramp.text.2}',
fontWeight: '{core.font.weight.medium}',
lineHeight: '{core.font.leading.snug}',
letterSpacing: '{core.font.tracking.normal}',
fontOpticalSizing: '{core.font.opticalSizing.auto}',
},
sm: {
fontFamily: '{core.font.family.sans}',
fontSize: '{core.type.ramp.text.1}',
fontWeight: '{core.font.weight.medium}',
lineHeight: '{core.font.leading.snug}',
letterSpacing: '{core.font.tracking.wide}',
fontOpticalSizing: '{core.font.opticalSizing.auto}',
},
},

code: {
md: {
fontFamily: '{core.font.family.mono}',
fontSize: '{core.type.ramp.text.2}',
fontWeight: '{core.font.weight.regular}',
lineHeight: '{core.font.leading.normal}',
letterSpacing: '{core.font.tracking.normal}',
fontVariantNumeric: '{core.font.numeric.tabular}',
},
sm: {
fontFamily: '{core.font.family.mono}',
fontSize: '{core.type.ramp.text.1}',
fontWeight: '{core.font.weight.regular}',
lineHeight: '{core.font.leading.normal}',
letterSpacing: '{core.font.tracking.normal}',
fontVariantNumeric: '{core.font.numeric.tabular}',
},
},
},
};

Rules of Engagement (non-negotiable)

  1. Semantic-only consumption: components use only text.* styles.
  2. No responsive logic in components: responsiveness lives in core ramps only.
  3. Tag ≠ style: HTML element choice is semantic; style choice is visual contract (text.*).
  4. No font-variation-settings by default: use standard properties (weight/width/optical sizing) unless you truly need a custom axis.
  5. Numeric stability in dashboards: use tabular numbers where values update frequently (text.code.* or a dedicated numeric style if needed).
  6. Robustness under user settings: UI must survive 200% text resize and user text spacing adjustments without losing content or functionality.

Theming

Themes may tune:

  • core ramps (core.type.ramp.*) — the responsive engine
  • font stacks (core.font.family.*)
  • weights (core.font.weight.*)
  • leading/tracking defaults (core.font.leading.*, core.font.tracking.*)
  • optional features (core.font.opticalSizing.*, core.font.numeric.*)

Semantic token names never change across themes.


Validation

Errors (validation must fail when)

  • leading order breaks:

    • tight >= snug
    • snug >= normal
    • normal >= relaxed
  • weight order breaks:

    • regular > medium
    • medium > semibold
    • semibold > bold
  • any emitted fontOpticalSizing value is not auto or none

  • generated output does not emit a viewport-safe fallback before container-based overrides

  • generated output does not gate container-based overrides behind @supports (width: 1cqi)

Warning (validation should warn when)

  • adjacent steps in the same semantic family resolve to the same effective text contract

Summary

  • Core defines primitives + responsive ramps (the engine)
  • Semantic defines a small canonical set: display/headline/title/body/label (+ code)
  • Components consume only semantic text.*
  • Responsiveness is native (ramp-based), not component logic
  • System is robust under accessibility stress (resize + text spacing)