Typography
Typography tokens are used to maintain a consistent set of font styles throughout our apps.
Font family
The default font stack is designed to be simple and highly available on as many devices as possible.
| Token | Value | Example |
|---|---|---|
--ahi-font-family-sans |
-apple-system, BlinkMacSystemFont, ‘Segoe UI’, Roboto, Helvetica, Arial, sans-serif, ‘Apple Color Emoji’, ‘Segoe UI Emoji’, ‘Segoe UI Symbol’ | The quick brown fox jumped over the lazy dog. |
--ahi-font-family-mono |
SFMono-Regular, Consolas, ‘Liberation Mono’, Menlo, monospace; | The quick brown fox jumped over the lazy dog. |
Type roles
A role is what the text is — running text, a form label, a timestamp — rather than how big
it is. Each one is a size and the leading it is meant to be set on, published as a pair:
--ahi-type-<role>-font-size and --ahi-type-<role>-line-height.
Reach for a role first. The numeric scale below still exists and still works, but it asks you to make two decisions that belong together, and the library has the scars to show for it: 14 distinct size/line-height combinations, 14px set against four different line heights, and 15px set inside a 16px line box — which is how a component shipped clipping its own descenders. A role gives you the pair, already checked.
| Role | What it is | Compact | Example |
|---|---|---|---|
display |
Page-level titles. | 30 / 36 | Aa |
heading |
Section titles. | 20 / 28 | Aa |
subheading |
Card and group titles. | 16 / 24 | Aa |
body |
Running text, and most of the UI. | 14 / 20 | Aa |
body-emphasis |
Body, weight-shifted. Same metrics; differs by weight alone. | 14 / 20 | Aa |
label |
Form labels, table headers. | 14 / 20 | Aa |
caption |
Help text, timestamps, metadata. | 12 / 16 | Aa |
code |
Monospace, inline and block. | 13 / 20 | Aa |
The body-emphasis role carries body’s metrics deliberately — it differs by weight
alone. Apply --ahi-font-weight-semibold alongside it.
Density decides the size
The values above are the compact tier: clinical density, which is the default. Type size is keyed on who is reading, not on how wide the window is — a clinician’s phone wants the densest text in the system, and a clinic kiosk on a large screen wants the largest.
| Role | Compact clinical — EHR, MyPractice |
Comfortable patient-facing — portal, forms |
Spacious kiosk — read standing, at arm’s length |
|---|---|---|---|
display |
30 / 36 | 34 / 40 | 36 / 44 |
heading |
20 / 28 | 24 / 32 | 30 / 36 |
subheading |
16 / 24 | 18 / 26 | 20 / 28 |
body |
14 / 20 | 16 / 24 | 18 / 28 |
body-emphasis |
14 / 20 | 16 / 24 | 18 / 28 |
label |
14 / 20 | 16 / 24 | 18 / 28 |
caption |
12 / 16 | 14 / 20 | 16 / 24 |
code |
13 / 20 | 14 / 22 | 16 / 24 |
Setting the tier
Not every app owns its <body>, so a tier can be applied at whichever level you actually
control. Custom properties inherit — including across the shadow boundary — so all three work with no extra
machinery, and a tier scopes to its subtree: a dense table inside a comfortable page can opt back out.
The whole app, when you control the document:
<html class="ahi-density-comfortable"></html>
One region, inside a shell you do not own:
<div class="ahi-density-comfortable">...</div>
A single component, when you own nothing else. Set the role tokens directly — a class cannot reach a component’s internals, but an inherited custom property can:
<ahi-card
style="
--ahi-type-subheading-font-size: 18px;
--ahi-type-subheading-line-height: 26px;
"
>
...
</ahi-card>
Compact is the default and has no class — applying nothing changes nothing.
Set a tier as high up as you can. Overriding role tokens on a single component pins that component to one size — it stops responding to the tier around it, which is the behavior the tiers exist to provide.
Scaling with a user’s text-size preference
--ahi-font-scale multiplies the roles exactly as it multiplies the
numeric scale, sizes and line heights together. It must be set on :root to take effect: CSS
substitutes a var() inside a custom property’s value at the element where that property is
declared, so setting the scale further down the tree cannot reach back into a role.
:root {
--ahi-font-scale: 1.35;
}
Font size
Eight steps. Prefer a type role — a role pairs a size with the leading it is meant to be set on, and a raw step is a size with no opinion about the line box it sits in.
| Token | Value | Example |
|---|---|---|
--ahi-font-size-300 |
12px | Aa |
--ahi-font-size-350 |
14px | Aa |
--ahi-font-size-400 |
16px | Aa |
--ahi-font-size-450 |
18px | Aa |
--ahi-font-size-500 |
20px | Aa |
--ahi-font-size-600 |
24px | Aa |
--ahi-font-size-750 |
30px | Aa |
--ahi-font-size-900 |
36px | Aa |
Retired steps
There were fifteen, eight of them a single pixel from their neighbor, and nothing said which of a pair to
reach for — which is how <ahi-checkbox> came to render its label at 15px while
<ahi-radio> rendered the same label at 14px.
These seven still resolve, so nothing breaks on upgrade, but they are deprecated and will be removed in the next major. Each folds to the survivor a type role already sits on rather than to the arithmetic nearest, because four of them sit exactly between two survivors.
| Retired | Was | Now resolves to | |
|---|---|---|---|
--ahi-font-size-275 |
11px | --ahi-font-size-300 (12px) |
the caption role |
--ahi-font-size-325 |
13px | --ahi-font-size-350 (14px) |
the body role |
--ahi-font-size-375 |
15px | --ahi-font-size-350 (14px) |
the label role |
--ahi-font-size-425 |
17px | --ahi-font-size-400 (16px) |
the subheading role |
--ahi-font-size-550 |
22px | --ahi-font-size-600 (24px) |
— |
--ahi-font-size-800 |
32px | --ahi-font-size-750 (30px) |
the display role |
--ahi-font-size-1000 |
40px | --ahi-font-size-900 (36px) |
— |
Font weight
| Token | Value | Example |
|---|---|---|
--ahi-font-weight-regular |
400 | The quick brown fox jumped over the lazy dog. |
--ahi-font-weight-medium |
500 | The quick brown fox jumped over the lazy dog. |
--ahi-font-weight-semibold |
600 | The quick brown fox jumped over the lazy dog. |
--ahi-font-weight-bold |
700 | The quick brown fox jumped over the lazy dog. |
:::tip These were 450 / 550 / 650 / 700 before this release. On a variable font — macOS resolves the stack
to SF Pro — those are four distinct weights, so the scale looked correct. On a
static family it collapses: CSS font matching walks available faces upward for any
request above 500, so against Windows’ Segoe UI, 550 landed on Semibold and 650 landed on
Bold. semibold and bold painted the same weight on the platform
most clinicians use. 500 is safe where 550 was not, because a request at or below 500 searches downward
instead. :::
Letter spacing
| Token | Value | Example |
|---|---|---|
--ahi-letter-spacing-densest |
-0.54px | The quick brown fox jumped over the lazy dog. |
--ahi-letter-spacing-denser |
-0.3px | The quick brown fox jumped over the lazy dog. |
--ahi-letter-spacing-dense |
-0.2px | The quick brown fox jumped over the lazy dog. |
--ahi-letter-spacing-normal |
0px | The quick brown fox jumped over the lazy dog. |
Line height
| Token | Value | Example |
|---|---|---|
--ahi-line-height-300 |
12px |
The quick brown fox jumped over the lazy dog.
The quick brown fox jumped over the lazy dog. The quick brown fox jumped over the lazy dog. |
--ahi-line-height-400 |
16px |
The quick brown fox jumped over the lazy dog.
The quick brown fox jumped over the lazy dog. The quick brown fox jumped over the lazy dog. |
--ahi-line-height-500 |
20px |
The quick brown fox jumped over the lazy dog.
The quick brown fox jumped over the lazy dog. The quick brown fox jumped over the lazy dog. |
--ahi-line-height-600 |
24px |
The quick brown fox jumped over the lazy dog.
The quick brown fox jumped over the lazy dog. The quick brown fox jumped over the lazy dog. |
--ahi-line-height-700 |
28px |
The quick brown fox jumped over the lazy dog.
The quick brown fox jumped over the lazy dog. The quick brown fox jumped over the lazy dog. |
--ahi-line-height-800 |
32px |
The quick brown fox jumped over the lazy dog.
The quick brown fox jumped over the lazy dog. The quick brown fox jumped over the lazy dog. |
--ahi-line-height-1000 |
40px |
The quick brown fox jumped over the lazy dog.
The quick brown fox jumped over the lazy dog. The quick brown fox jumped over the lazy dog. |
--ahi-line-height-1200 |
48px |
The quick brown fox jumped over the lazy dog.
The quick brown fox jumped over the lazy dog. The quick brown fox jumped over the lazy dog. |