Typography
Font families, geometric type scale, weight, line-height, and semantic text tokens for consistent, accessible text styling.Overview
Typography is built on a geometric type scale: base size × ratio^step, with 14px and 1.2 as defaults. Every text style is a semantic token that composes font size, weight, and line-height, so components express intent (heading, body, label) rather than raw values.Two layers work together: raw size tokens (--font-size-xs … --font-size-5xl) form the geometric scale, and semantic type scale tokens (--text-heading-1-size, --text-body-leading, etc.) reference them by var(). Themes override the entire scale by adjusting base and ratio in defineTheme; all semantic tokens recompute automatically.Font Families
Three font roles: body (UI text), heading (titles and headings), and code (monospace). By default, body and heading share the same system font stack; code uses a monospace stack. Custom themes can assign different families per role; heading inherits from body when not explicitly set.| Token | Value |
|---|---|
| --font-family-body | var(--font-figtreeThe quick brown fox jumps over the lazy dog |
| --font-family-code | "SF Mono"The quick brown fox jumps over the lazy dog |
| --font-family-heading | var(--font-figtreeThe quick brown fox jumps over the lazy dog |
Font Sizes
Geometric scale: round(base × ratio^step), expressed in rem. The default scale is 14px × 1.2, producing 12 steps from 4xs (6px) to 5xl (42px). Adjusting base and ratio in defineTheme regenerates every size token while preserving proportional relationships.| Token | Value |
|---|---|
| --font-size-4xs | 0.375remThe quick brown fox jumps over the lazy dog |
| --font-size-3xs | 0.4375remThe quick brown fox jumps over the lazy dog |
| --font-size-2xs | 0.5remThe quick brown fox jumps over the lazy dog |
| --font-size-xs | 0.625remThe quick brown fox jumps over the lazy dog |
| --font-size-sm | 0.75remThe quick brown fox jumps over the lazy dog |
| --font-size-base | 0.875remThe quick brown fox jumps over the lazy dog |
| --font-size-lg | 1.0625remThe quick brown fox jumps over the lazy dog |
| --font-size-xl | 1.25remThe quick brown fox jumps over the lazy dog |
| --font-size-2xl | 1.5remThe quick brown fox jumps over the lazy dog |
| --font-size-3xl | 1.8125remThe quick brown fox jumps over the lazy dog |
| --font-size-4xl | 2.1875remThe quick brown fox jumps over the lazy dog |
| --font-size-5xl | 2.625remThe quick brown fox jumps over the lazy dog |
Font Weights
Four semantic weights: normal (400, body/code), medium (500, labels/data), semibold (600, headings/titles), bold (700, strong emphasis). Type scale tokens reference these by var() so themes can remap numeric values.| Token | Value |
|---|---|
| --font-weight-normal | 400The quick brown fox jumps over the lazy dog |
| --font-weight-medium | 500The quick brown fox jumps over the lazy dog |
| --font-weight-semibold | 600The quick brown fox jumps over the lazy dog |
| --font-weight-bold | 700The quick brown fox jumps over the lazy dog |
Line Height
Line heights are computed from a tiered target ratio and snapped to a 4px vertical grid. Small text (<20px) targets 1.5, medium text (20–31px) targets 1.4, and large text (≥32px) targets 1.25. A minimum gap of fontSize + 4px is enforced. The result is a unitless ratio stored in each --text-*-leading token.The 4px grid matters: every line box aligns to 4px increments, which keeps baselines, spacing, and component heights predictable. The expandTypeScale utility computes these automatically when you provide a base and ratio, so you should never need to set line-height manually.Type Scale
Semantic tokens that combine size, weight, and line-height into a single type style. Each token triplet (--text-*-size, --text-*-weight, --text-*-leading) is consumed by XDSText and XDSHeading. Use the component props rather than composing raw font tokens.| Sample | Tokens |
|---|---|
| H1 | var(--font-size-2xl) · var(--font-figtreevar(--font-weight-semibold) · 1.3333 |
| H2 | var(--font-size-xl) · var(--font-figtreevar(--font-weight-semibold) · 1.4 |
| H3 | var(--font-size-lg) · var(--font-figtreevar(--font-weight-semibold) · 1.4118 |
| H4 | var(--font-size-base) · var(--font-figtreevar(--font-weight-semibold) · 1.4286 |
| H5 | var(--font-size-sm) · var(--font-figtreevar(--font-weight-semibold) · 1.6667 |
| H6 | var(--font-size-xs) · var(--font-figtreevar(--font-weight-semibold) · 1.6 |
| Display 1 | var(--font-size-5xl) · var(--font-figtreevar(--font-weight-semibold) · 1.2381 |
| Display 2 | var(--font-size-4xl) · var(--font-figtreevar(--font-weight-semibold) · 1.2571 |
| Display 3 | var(--font-size-3xl) · var(--font-figtreevar(--font-weight-semibold) · 1.2414 |
| Large | var(--font-size-lg) · var(--font-figtreevar(--font-weight-semibold) · 1.4118 |
| Body | var(--font-size-base) · var(--font-figtreevar(--font-weight-normal) · 1.4286 |
| Label | var(--font-size-base) · var(--font-figtreevar(--font-weight-medium) · 1.4286 |
| Code | var(--font-size-base) · "SF Mono"var(--font-weight-normal) · 1.4286 |
| Supporting | var(--font-size-sm) · var(--font-figtreevar(--font-weight-normal) · 1.6667 |
Display Text
Display variants (display-1, display-2, display-3) continue the geometric progression above heading-1, at steps +6, +5, and +4. They use normal weight (400) instead of semibold, and tighter line-heights (~1.2), since large text reads better with less leading. Use display types for hero banners, marketing headlines, and data callouts, not for document headings.Display text often needs heading semantics for accessibility. Use the type prop on XDSHeading to apply display styling while preserving the correct HTML element: <XDSHeading level={1} type="display-1"> gives you display-1 styling with an <h1> tag, so screen readers see the correct document outline.Usage
XDSHeading for document structure
tsximport {XDSHeading} from '@xds/core';// Heading levels map to semantic tokens: level 1 → --text-heading-1-*<XDSHeading level={1}>Page Title</XDSHeading><XDSHeading level={2}>Section</XDSHeading><XDSHeading level={3}>Subsection</XDSHeading>// Display type for hero/marketing headings — level sets the HTML element<XDSHeading level={1} type="display-1">Hero Title</XDSHeading><XDSHeading level={2} type="display-2">$1.2M Revenue</XDSHeading>// Override the accessibility level when visual ≠ document hierarchy<XDSHeading level={2} accessibilityLevel={3}>Sidebar Section</XDSHeading>
XDSText for body, label, and display text
tsximport {XDSText} from '@xds/core';<XDSText type="body">Body text at the base scale.</XDSText><XDSText type="large">Emphasized body text.</XDSText><XDSText type="label">Form label</XDSText><XDSText type="supporting">Helper text, timestamps, metadata.</XDSText><XDSText type="code">{'const x = 1;'}</XDSText>// Display without heading semantics (data callouts, decorative)<XDSText type="display-2">$1.2M Revenue</XDSText>
Customizing the type scale via defineTheme
tsximport {defineTheme} from '@xds/core';// Adjust the entire ramp holistically with base and ratioconst editorialTheme = defineTheme({name: 'editorial',typography: {scale: { base: 16, ratio: 1.25 }, // airy / article feelbody: { family: 'Geist', fallbacks: '-apple-system, sans-serif' },heading: { weight: 'bold' },code: { family: 'Geist Mono', fallbacks: '"SF Mono", monospace' },},});const denseTheme = defineTheme({name: 'dense',typography: {scale: { base: 12, ratio: 1.125 }, // compact / data-dense UI},});
Best Practices
| Guidance | Practices |
|---|---|
| Do | Use XDSHeading for document headings and XDSText for everything else; they apply the full type scale automatically. |
| Do | Adjust typography holistically: change base and ratio in defineTheme to shift the entire ramp (e.g. { base: 16, ratio: 1.25 } for editorial, { base: 12, ratio: 1.125 } for dense UI). |
| Do | Use display types with as="h1" (or h2/h3) when display text is a page heading; this preserves accessibility while giving you display-level sizing. Or better, use <XDSHeading level={1} type="display-1"> which handles both semantics and styling. |
| Do | Let line-height snap to the 4px grid via the type scale; expandTypeScale computes leading automatically from base and ratio. |
| Do | Use the supporting type for secondary information: timestamps, helper text, metadata, captions. |
| Do | Use accessibilityLevel on XDSHeading when the visual hierarchy doesn't match the document outline (e.g. sidebar or card headings). |
| Don't | Set font-size or line-height manually; use the semantic type scale tokens so the full ramp stays consistent and 4px-grid-aligned. |
| Don't | Skip heading levels (e.g. h1 to h3); screen readers rely on an unbroken hierarchy. Use accessibilityLevel to decouple visual from semantic level. |
| Don't | Use display types for body content or in-page sections; they're designed for hero/marketing/data-callout contexts only. |
| Don't | Override individual size tokens (--font-size-lg) to "tweak" a heading; adjust base/ratio instead so proportions remain coherent across the entire scale. |
| Don't | Use raw numeric font-weight values (400, 600); reference the semantic weight tokens so themes can remap them. |