Typography
Type scale, weights, line heights, and the safe zone
Font family
Daybridge uses Inter Variable as its primary typeface on the web. On iOS and Android, we use the platform system fonts (SF Pro and Roboto respectively).
"InterVariable", "SF Pro Display", -apple-system, BlinkMacSystemFont,
"Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans",
"Helvetica Neue", sans-serifText rendering
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-rendering: optimizeLegibility;Type scale
The standard UI size is 13px on desktop and 15px on touch surfaces. The scale uses ten steps; 3xs is the floor, for micro labels only, and is the one size that does not grow in the marketing tier:
Tiers
The same tokens resolve to different regimes: the app-scale and marketing-scale scope classes swap what every utility means inside their subtree, so shared components render correctly under either. The app tier is the default; scopes nest and the innermost wins. Never use rem-based type (text-base) — the root font size can't be scoped.
The touch tier
touch-scale layers over the app tier for phone and tablet surfaces. It is not a third scale: every step already resolves to a size a touch surface wants, so the only thing it moves is the resting size, from 13px to text-md's 15px.
| App | Touch | |
|---|---|---|
| base (unsized text) | 13px | 15px |
That single change is what separates a scale read at arm's length on a desk from one read on a held device. Apply it alongside the app tier (class="app-scale touch-scale"), not instead of it.
Each tier's resting size is a different step, and in the marketing tier it is not a step at all:
| Tier | Resting size | Named step |
|---|---|---|
| App | 13px | text-sm |
| Touch | 15px | text-md |
| Marketing | 16px | none |
Where the resting size has a name, prefer that name to inheriting it. Text with no size class takes its ancestor's size, not the tier's resting size, so inside a heading or any other sized subtree there is no way back to body size — and because the padding tokens are line-height-relative, a control that inherits the wrong size changes shape as well as type. Omit the size only in components that must follow whichever tier they are rendered under.
On touch surfaces that gives:
| Role | Class |
|---|---|
| Body, list rows, controls | text-md |
| Emphasised body, row headers | text-md font-emphasis |
| Captions, subtitles, section headers | text-sm |
| Micro labels, overlines | text-2xs |
| Large body, inputs | text-lg |
| Page headers | text-xl |
| Display | text-3xl |
Emphasis is a weight, never a size bump: a list row and the header above it are the same size, separated by font-emphasis.
Size and weight are intrinsically linked — the weight shown in the scale table is what the size renders at, never something to set by hand. Body sizes rest at the tier's resting weight; the heading sizes (text-xl and above) carry 500 in both tiers via the size utility itself.
| Weight | App | Marketing |
|---|---|---|
| Resting (inherited) | 500 | 450 |
text-xl – text-4xl (intrinsic) | 500 | 500 |
font-emphasis | 600 | 550 |
Line height
Each font size has a set of line-height values depending on context. All are derived from the font size using CSS round() and clamp(), producing values snapped to a pixel grid. These tokens are the only sanctioned line heights — stock utilities (leading-tight, leading-snug, leading-relaxed, …) and arbitrary values should not be used.
Component line-height
For single-line UI elements — sidebar items, buttons, table cells, labels. Tight, just enough to contain the glyphs. This is the global default.
line-height: round(up, 1em + clamp(2px, 1em - 12px, 8px), 2px);Reading line-height
For multi-line content — event descriptions, editor text, modal body copy. Generous, optimised for comfortable reading.
line-height: round(up, 1em + clamp(4px, 1em - 8px, 12px), 4px);Airy line-height
For spacious multi-line content where reading still feels dense — long descriptions and editor bodies that benefit from extra breathing room.
line-height: round(up, 1em + clamp(6px, 1em - 4px, 16px), 4px);Display line-height
For hero and display type above the component scale, where even the component formula's +8px cap reads loose. A bare multiplier snapped to the grid.
line-height: round(up, 1.05em, 4px);Exact line boxes
leading-none (a 1em line box) is the sanctioned exception for single-line labels that need the line box to match the glyphs exactly — time labels positioned against a grid, avatar initials, badge numerals. The formulas can't produce it (component's floor is +2px), and it should never appear on anything that can wrap.
Padding around text
When text appears inside a boxed element — a button, tag, badge, or input — the component line-height defines a safe zone around the glyphs. Padding extends outward from this using the lh unit, which resolves to the element's computed line-height. This means padding scales automatically with text size — no per-size values needed.
Six density tiers, named with t-shirt sizes to match the radius and shadow scales:
/* xs — tags, badges, inline labels */
padding: 0.075lh 0.375lh;
/* sm — small buttons, chips, dense controls */
padding: 0.125lh 0.5lh;
/* md — buttons, standard controls */
padding: 0.25lh 0.625lh;
/* lg — prominent actions, large buttons */
padding: 0.4lh 0.875lh;
/* xl — text inputs, search fields */
padding: 0.625lh 0.875lh;
/* 2xl — touch controls */
padding: 0.8lh 1lh;Both axes use lh (line-height-relative) so all padding scales proportionally with text size.
| text-sm | text-md | text-lg | |
|---|---|---|---|
| xs | Label | Label | Label |
| sm | Label | Label | Label |
| md | Label | Label | Label |
| lg | Label | Label | Label |
| xl | Label | Label | Label |
| 2xl | Label | Label | Label |
Setting element heights directly should be avoided. Instead, height is determined by the safe zone:
element height = line-height + (2 × vertical padding) + borderInline congruence
When a badge or tag appears inline with text, its total height must fit within the surrounding text's reading line-height. Otherwise it pushes adjacent lines apart.
Rule: use one step down from the surrounding text size.
Text colors
Three semantic color tokens control text contrast. Each carries a descending semantic chroma multiplier, so in hued contexts vividness descends with emphasis instead of peaking at low contrast; neutral contexts are unaffected. Each maps to a luminance shade, and the resolved color depends on the color mode applied to the element — neutral text is gray, brand text takes the brand hue, and so on. See the colors guide for how semantic colors work.
| Light | Dark | |||||||
|---|---|---|---|---|---|---|---|---|
| Shade | Alpha | ×C | Shade | Alpha | ×C | |||
| Text | ||||||||
| high-contrast | 15 | 1 | 0.9 | white | 1 | 1 | ||
| medium-contrast | 11 | 1 | 0.55 | 5 | 1 | 0.55 | ||
| low-contrast | 9 | 1 | 0.25 | 8 | 1 | 0.25 | ||