Skip to content

Typography

Typography in helix-ui is four token groups: family, size, weight, and line-height. Everything you set on text resolves to one of them. There is no free-typed font-size: 15px in component CSS — if a size is missing, it isn’t in the system.

Font family

Two families ship. The default (sans) leads with Inter and falls back through the OS UI fonts; mono is a system monospace stack.

TokenValue
--helix-ui-font-family-sansInter, ui-sans-serif, system-ui, -apple-system, Segoe UI, Helvetica, Arial, sans-serif
--helix-ui-font-family-monoui-monospace, SFMono-Regular, Menlo, Consolas, monospace

Inter is expected but never required. If it hasn’t loaded, ui-sans-serif / system-ui render the same metrics closely enough that layout doesn’t shift. Use mono for code, keyboard glyphs, and anything that needs column alignment (timestamps, IDs, numeric tables).

CJK fallback

Inter carries no Hangul, Kana, or Han glyphs. A mixed-script line like 제품·AI 프롬프트 would otherwise render the Latin in Inter and the Hangul through whatever OS CJK font the browser picks — usually a size larger — so the line visibly jumps mid-word. The fix is to put an OS CJK font ahead of Inter per language, chosen so its Latin glyphs match its CJK metrics. From apps/site/src/site.css:

:lang(ko) {
font-family:
'Pretendard Variable', Pretendard, 'Apple SD Gothic Neo', 'Malgun Gothic', 'Noto Sans KR',
Inter, ui-sans-serif, system-ui, sans-serif;
word-break: keep-all; /* Korean breaks at spaces, never inside a word */
line-break: strict;
overflow-wrap: anywhere;
}
:lang(ja) {
font-family:
'Hiragino Sans', 'Hiragino Kaku Gothic ProN', 'Yu Gothic', 'Meiryo', 'Noto Sans JP', Inter,
ui-sans-serif, system-ui, sans-serif;
word-break: normal; /* CJK with no spaces: let kinsoku break anywhere */
line-break: strict;
}
:lang(zh) {
font-family:
'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', 'Noto Sans SC', Inter, ui-sans-serif,
system-ui, sans-serif;
word-break: normal;
line-break: strict;
}

Two rules ride along with the stack. Korean keeps word-break: keep-all because its words are space-separated — breaks land on spaces, not inside a word. Japanese and Chinese use word-break: normal so the browser’s kinsoku-shori (line-break: strict) can break between characters instead of freezing a whole sentence and overflowing. Phrase-level stickiness (읽을 수 있는, AI の) is joined with NBSP at the translation layer — CSS can’t infer a phrase boundary.

Size scale

Eight steps, xs through 4xl, anchored on a 16px body. The full range spans 3× — 12px caption to 36px display.

TokenSizeRole
--helix-ui-font-size-xs12pxCaption, metadata
--helix-ui-font-size-sm14pxCompact body, dense tables
--helix-ui-font-size-md16pxDefault body
--helix-ui-font-size-lg18pxLead paragraph
--helix-ui-font-size-xl20pxSubheading
--helix-ui-font-size-2xl24pxSection heading
--helix-ui-font-size-3xl30pxPage heading
--helix-ui-font-size-4xl36pxDisplay

On viewports ≤767px the static build shrinks the top of the scale about 10% — md stays readable at 14px, 4xl drops to 32px — via a @media (max-width: 767px) override on the same variables. Components don’t opt in; they inherit it because they read the token.

Weight

Four weights. Inter ships all of them; the fallbacks synthesize what they lack.

TokenValueUse for
--helix-ui-font-weight-regular400Body copy
--helix-ui-font-weight-medium500Slight emphasis, labels
--helix-ui-font-weight-semibold600Buttons, headings
--helix-ui-font-weight-bold700Strong emphasis

Headings sit at semibold (600), not bold. Bold is loud enough that reserving it for the rare strong emphasis keeps it meaningful. There is no 300 or 800 — thin text fails contrast at small sizes, and 800 reads the same as 700 in most UI contexts.

Line-height

Three ratios, unitless so they scale with whatever size they land on.

TokenValueUse for
--helix-ui-font-lineHeight-tight1.2Headings, large text
--helix-ui-font-lineHeight-normal1.5Body — the default
--helix-ui-font-lineHeight-loose1.75Long-form reading

The pairing rule: as size goes up, line-height comes down. Body at 16px wants 1.5 for readability; a 30px heading at 1.5 leaves a gap you could park a car in, so headings take tight (1.2). The Text component encodes this automatically — see below.

The Text component

Text maps props to tokens so you never touch a variable directly for prose. size and weight alias the scales above; tone aliases the color roles from Color.

import { Text } from '@helix-ui/core';
<Text as="h1" size="3xl" weight="semibold">Billing</Text>
<Text size="md">Your plan renews on the 1st.</Text>
<Text size="sm" tone="muted">Last synced 2 minutes ago</Text>

One behavior worth knowing: at xl and above, Text switches its own line-height to tight — so <Text size="2xl"> is 24px at 1.2, while <Text size="md"> is 16px at 1.5. You get the size/line-height pairing without setting it.

Reach for raw tokens only outside prose — a custom layout, a third-party surface:

.doc-callout {
font-family: var(--helix-ui-font-family-sans);
font-size: var(--helix-ui-font-size-sm);
font-weight: var(--helix-ui-font-weight-medium);
line-height: var(--helix-ui-font-lineHeight-normal);
}

Typography can move — via DNA

The DNA typography gene rebinds the family and rescales sizes at runtime. Its wildtype allele is sans (Inter, scale 1.0); others swap the whole stack — grotesk → Geist, rounded → Nunito, serif → Lora, mono → a monospace UI. A separate typeScale gene sets the ratio between steps (wildtype 1.2); major (1.25) or golden (1.618) widen the gap between body and display without you editing a single size.

Because sizes are re-derived from md × ratio^step, changing the gene rewrites --helix-ui-font-size-* on the DNA provider’s wrapper. Every Text and every component under it follows through the cascade with no re-render.

Usage rules

  1. Body is md/16px at normal/1.5. Drop to sm/14px only in dense UI (tables, side panels), never for primary reading.
  2. Headings are semibold, not bold. Save 700 for the one word that has to shout.
  3. Let size drive line-height. Big text takes tight; don’t set 1.5 on a heading. Text does this for you above lg.
  4. Never inline a px size. If the value you want isn’t a token, the scale is telling you to pick the neighbor.
  5. Monospace for anything that aligns. Numeric columns, IDs, and diffs read wrong in a proportional font.

The machine-readable font tokens — DTCG types, build outputs, the naming grammar — live in the Tokens reference.