Typography
Two type roles — Heading and Body — each with its own typeface, size steps, and weight. Consistent composite styles for every typographic decision in product UI.
Typeface decisions
Screen-optimised alternatives, not the brand identity fonts
Lora (Heading) and Hanken Grotesk (Body) are not Open Point's brand typefaces — they're the deliberate screen-crafted alternatives to them, chosen specifically for product UI. Brand identity type lives in marketing and print contexts; it isn't optimised for rendering legibly at small sizes across every browser, OS, and screen density a government stakeholder might be using, and isn't licensed for that kind of broad, ongoing web delivery. Lora and Hanken Grotesk are the pair actually shipped to the browser — every size step and weight documented on this page describes what renders in the product, not the brand system.
Weight: Regular, Semibold, and a heavier Bold than the shared scale
Body text has three real weights — Regular (400) for default copy,
Semibold (500) for inline emphasis within a paragraph, and
Bold (700) for stronger emphasis. Bold intentionally renders heavier than
the shared Font_Weight.Bold scale value (600) — 600 read as too subtle at
body text sizes to function as real emphasis, so Body's Bold style uses 700 directly
rather than the shared token.
Heading styles render at 500 (Semibold weight) with slightly tightened
tracking (-0.8% letter-spacing, i.e. -0.008em) — a deliberate,
confirmed decision for a lighter, more refined heading feel than a full Bold would give at
heading sizes. The shared Font_Weight.Heading token and the hand-set
--wa-font-weight-heading CSS variable both now resolve to 500 too, aliasing
Font_Weight.Semibold rather than Font_Weight.Bold.
Font stacks: why Noto Sans / Noto Serif sit in the fallback chain
Both stacks carry a deliberate multi-lingual fallback before dropping to generic system fonts:
--orbit-font-family-body: 'Hanken Grotesk', 'Noto Sans', ui-sans-serif, system-ui, sans-serif;
--orbit-font-family-heading: 'Lora', 'Noto Serif', ui-serif, serif; Content in a language Hanken Grotesk or Lora doesn't cover well needs to render legibly before falling all the way back to whatever generic font a user's OS happens to substitute. Noto Sans and Noto Serif were chosen specifically for their broad Unicode/script coverage, and each is matched to its stack's own style (serif fallback for the serif Heading stack, sans for the sans Body stack) so a missing glyph doesn't also mean a jarring style switch.
--orbit-font-family-body / --orbit-font-family-heading are the
source of truth — --wa-font-family-body / --wa-font-family-heading
just alias them, so every Web Awesome component picks up the same typeface automatically.
Override the --orbit-* variable, not the --wa-* one, if you ever
need to change the typeface for a consuming app — same pattern as
--orbit-color-* and --orbit-elevation-* elsewhere in the theme.
Adding these fonts to a project
Both typefaces are Google Fonts. Consumers of
@open-point-design/orbit-theme get them for free — its published
theme.css loads them via @import, so no separate step is
needed alongside --wa-font-family-body / --wa-font-family-heading.
If you're wiring up these fonts without the package (e.g. an app not yet on
orbit-theme), add the following to the page <head>
instead — <link> tags preload sooner than a CSS @import
and are the better choice when you control the HTML directly. This includes Noto
Sans/Noto Serif alongside the primary typefaces — load the fallback fonts
too, not just Hanken Grotesk/Lora, or the multi-lingual fallback described
above is decorative: Noto isn't preinstalled on most systems, so an unloaded font is
just skipped straight past to ui-sans-serif/ui-serif.
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Hanken+Grotesk:wght@300;400;500;600;700&family=Noto+Sans:wght@300;400;500;600;700&family=Lora:wght@400;500&family=Noto+Serif:wght@400;500&display=swap"
rel="stylesheet"
/> The weight lists match what Orbit actually uses — Hanken Grotesk 300–700 covers Body's Regular/Semibold/Bold styles, and Lora 400/500 covers Heading's Semibold weight plus a Regular fallback; Noto Sans/Noto Serif mirror the same weights as their primary counterpart. If your app genuinely doesn't need multi-lingual coverage, the Noto families can be dropped to shrink the font payload — but that's a deliberate trade-off against the reason they're in the stack, not a default.
What's actually wired into CSS today
Only a handful of hand-set globals in component-overrides.css are live in the
published theme: --orbit-font-family-body / --orbit-font-family-heading
(aliased onto --wa-font-family-body / --wa-font-family-heading),
and --wa-font-weight-{normal, semibold, bold, body, heading, action}.
The full composite type scale below — every Heading and Body style's exact size, weight,
tracking, and line-height combination — is real, current, and drives this page directly
from the token source, but isn't yet exposed as its own set of consumable
--orbit-text-* CSS custom properties. Until it is, use the family/weight
globals above directly rather than hand-assembling a composite style from the primitive
tables in the Tokens tab.
Type styles
Each type role pairs a typeface with a fixed set of sizes, weights, and line heights.
Heading uses Lora at Semibold (500) for page and section titles, and
Body uses Hanken Grotesk for paragraph and UI copy — every Body size has
Regular, Semibold, and Bold variants for emphasis within copy. See
Typeface decisions above for why these are the
screen typefaces rather than the brand fonts, and for the weight and font-stack
reasoning. Specimens below render with the token's real font family — if the typeface
isn't installed locally, the browser falls back through the same stack shipped in
production.
Heading
Body
Typography tokens
The primitive building blocks behind the Heading and Body styles — font family, size, weight, letter spacing, and line height. Click any token to copy it.
Font family
| Token | Value |
|---|---|
--orbit-font-family-Heading | Lora |
--orbit-font-family-Body | Hanken Grotesk |
Font size
| Token | Value |
|---|---|
--orbit-font-size-10 | 10px |
--orbit-font-size-11 | 11px |
--orbit-font-size-12 | 12px |
--orbit-font-size-14 | 14px |
--orbit-font-size-16 | 16px |
--orbit-font-size-20 | 20px |
--orbit-font-size-22 | 22px |
--orbit-font-size-24 | 24px |
--orbit-font-size-28 | 28px |
--orbit-font-size-32 | 32px |
--orbit-font-size-36 | 36px |
--orbit-font-size-40 | 40px |
--orbit-font-size-48 | 48px |
--orbit-font-size-56 | 56px |
--orbit-font-size-64 | 64px |
--orbit-font-size-72 | 72px |
--orbit-font-size-80 | 80px |
--orbit-font-size-96 | 96px |
Font weight
| Token | Value |
|---|---|
--orbit-font-weight-300 | 300 |
--orbit-font-weight-400 | 400 |
--orbit-font-weight-500 | 500 |
--orbit-font-weight-600 | 600 |
--orbit-font-weight-700 | 700 |
--orbit-font-weight-800 | 800 |
--orbit-font-weight-900 | 900 |
Letter spacing
| Token | Value |
|---|---|
--orbit-font-letter-spacing--50 | -50px |
--orbit-font-letter-spacing--25 | -25px |
--orbit-font-letter-spacing-0 | 0px |
--orbit-font-letter-spacing-25 | 25px |
--orbit-font-letter-spacing-50 | 50px |
--orbit-font-letter-spacing-75 | 75px |
--orbit-font-letter-spacing-100 | 100px |
Line height
| Token | Value |
|---|---|
--orbit-font-line-height-100 | 100px |
--orbit-font-line-height-110 | 110px |
--orbit-font-line-height-115 | 115px |
--orbit-font-line-height-120 | 120px |
--orbit-font-line-height-125 | 125px |
--orbit-font-line-height-130 | 130px |
--orbit-font-line-height-140 | 140px |
--orbit-font-line-height-150 | 150px |
--orbit-font-line-height-160 | 160px |
--orbit-font-line-height-175 | 175px |
--orbit-font-line-height-200 | 200px |
Leading scale
Four named leading multipliers for custom text compositions that don't map to one of the Heading or Body styles above — from tight display headlines to relaxed long-form paragraphs.
| Token | Value | When to use |
|---|---|---|
--op-leading-tight | 1.25 | Headlines and display text. Tight leading prevents visual gaps in large type. |
--op-leading-snug | 1.375 | Subheadings and compact UI labels where a little more air than tight is needed. |
--op-leading-normal | 1.5 | Short paragraphs, list items, UI copy. Standard readable rhythm. |
--op-leading-relaxed | 1.625 | Body copy and long-form text. The default for paragraphs — improves readability for sustained reading. |
Usage guidelines
Don't go below text-sm for interactive elements
--op-text-xs (12px) is appropriate for timestamps, badges, and captions —
passive text the user doesn't need to read carefully. Never use it for interactive labels,
form fields, error messages, or any text the user needs to act on. WCAG recommends 16px
minimum for body text; 14px (--op-text-sm) is acceptable for secondary
metadata.
Set line height to match the text's purpose
Use --op-leading-tight on headings, --op-leading-relaxed on
body paragraphs, and --op-leading-normal on UI labels and list items.
The default body style already uses --op-leading-relaxed — only override
it when you have a specific layout reason.
Not yet available outside this site. These --op-leading-*
tokens are real and work exactly as described — this page and the rest of Orbit's own
site use them directly — but they're only defined in Orbit's internal site CSS, not
published in @open-point-design/orbit-theme. A product team building in
Open Point or Social Point can't var(--op-leading-relaxed) today; treat this
guidance as the intended behaviour to replicate with your own line-height values until
this scale is published for consumption.
Never set text in all-caps at body size
All-caps text reduces readability by removing the ascender/descender cues that distinguish letters. Reserve it for short labels, section markers, and status badges at small sizes — never for sentences or paragraphs.
Do
Use --op-leading-relaxed (1.625) for all paragraph and body text. This is already the global default — preserve it rather than overriding it to a tighter value.
Don't
Set line-height: 1 or line-height: 1.1 on text elements for visual effect. This makes multi-line text unreadable and fails WCAG 1.4.12 (Text Spacing).
Do
Keep body text line length between 60–80 characters (roughly 60ch). Long lines force the eye to travel too far; short lines create an exhausting rhythm of line breaks.
Don't
Let body text span the full page width. Content wider than 80ch is measurably harder to read — use max-width on text containers, not on the page grid.
Accessibility
Minimum sizes
WCAG does not mandate a specific minimum font size, but best practice (and browser defaults)
establish 16px as the baseline for body text. In Open Point product UI, --op-text-base
(16px) is the minimum for body copy. Helper text, labels, and metadata may use
--op-text-sm (14px). --op-text-xs (12px) should be used only
for non-essential passive text (timestamps, version numbers, decorative labels).
Relative units
All size tokens use rem. This means they scale with the user's browser base
font size setting. A user who has set their browser to 20px base will see all text 25%
larger than the design baseline — this is intentional and required by WCAG 1.4.4 (Resize
Text). Never use px for font sizes in component styles.
Line height and text spacing
WCAG 1.4.12 (Text Spacing) requires that content remains readable when users override line height to 1.5× the font size, letter spacing to 0.12em, and word spacing to 0.16em. Using the Orbit leading tokens (which start at 1.25 for tight and reach 1.625 for relaxed) gives enough headroom that overrides at the WCAG levels won't break layouts.
Colour contrast on text
Text contrast requirements differ by size: normal text (below 18pt / 24px or 14pt / 18.67px
bold) requires 4.5:1 (AA) or 7:1 (AAA). Large text meets AA at 3:1. Always pair text
tokens with their intended background — --op-color-text-primary on
--op-color-bg-primary achieves AAA. See the
Colour page for contrast ratios across the palette.
Was this page helpful?