Typography
Seven fluid sizes, three font tokens, and a base layer that makes plain HTML read as designed — no classes required.
Scale
The scale is fluid — every step is a clamp() that breathes with the viewport; resize the window and every specimen below resizes with it.
| Token | Specimen | Default |
|---|---|---|
--k-text-xs |
The quick brown shipwright | clamp(0.78rem, 0.75rem + 0.15vw, 0.84rem) |
--k-text-s |
The quick brown shipwright | clamp(0.88rem, 0.85rem + 0.18vw, 0.95rem) |
--k-text-m |
The quick brown shipwright | clamp(1rem, 0.96rem + 0.22vw, 1.1rem) |
--k-text-l |
The quick brown shipwright | clamp(1.15rem, 1.06rem + 0.45vw, 1.35rem) |
--k-text-xl |
The quick brown shipwright | clamp(1.4rem, 1.22rem + 0.9vw, 1.8rem) |
--k-text-2xl |
The quick brown shipwright | clamp(1.8rem, 1.48rem + 1.6vw, 2.5rem) |
--k-text-3xl |
The quick brown shipwright | clamp(2.4rem, 1.9rem + 2.6vw, 3.6rem) |
Headings
Headings take the display font, weight, and tracking from
--k-font-display, --k-display-weight, and
--k-tracking — retune those three tokens and every
heading on the site follows. The design
page pairs them for you. Rendered live, h1 through h4:
Weights & styles
The scale is one axis; weight and style are the other. keel
uses exactly two weights — 400 for reading and
650 for emphasis and display — plus the italic,
underline, highlight, and mono treatments the base layer ships.
Every specimen below renders in the current pairing,
so this table is also how you audit a font choice: swap the
tokens on the design page and
re-read this page.
| Specimen | The CSS that produces it |
|---|---|
| Regular body — the reading weight | font-weight: 400 — the body default, no rule needed |
| Strong — the emphasis weight | strong { font-weight: 650 } — dt, labels, and th share it |
| Display — headings and stat values | font-weight: var(--k-display-weight) in var(--k-font-display) — one token, default 650 |
| Italic — a voice change, not a weight change | em — the browser's italic, untouched |
| The link style — accent, thin underline | a { color: var(--k-accent); text-decoration-thickness: 1px; text-underline-offset: 0.2em } |
| Small — a size step down and a shade softer | small { font-size: var(--k-text-s); color: var(--k-text-soft) } |
| A sentence with a marked phrase in it | mark { background: var(--k-accent-soft) } — derived, so always on-brand |
Inline code sits in running text |
code { font-family: var(--k-font-mono); background: var(--k-surface-2) } |
Why 650 and not "bold"? The system stacks carry
400 and 650–700 reliably on every OS,
and the six self-hosted faces ship exactly 400 +
700 — so keel never asks for a weight a font can't
honestly render.
Markup
<p>Regular body, <strong>strong</strong>, <em>italic</em>,
<a href="…">a link</a>, <small>small print</small>,
a <mark>marked phrase</mark>, and inline <code>code</code>.</p>
<!-- the display treatment is what every heading gets for free -->
<span style="font-family: var(--k-font-display);
font-weight: var(--k-display-weight);
letter-spacing: var(--k-tracking)">…</span>
Prose
All of this is the base layer — plain HTML, zero classes. A
paragraph with a link, strong
text, emphasis, small print, a
marked phrase, inline code, and a
Ctrl key.
A blockquote gets the accent edge, softer text, and the large step — quote something worth the room.
- An unordered list
- with sensible indentation
- and hairline gaps between items
- An ordered list
- gets the same treatment
- numbers included
Fonts
| Token | What it does | Default |
|---|---|---|
--k-font-display |
Headings and stat values. | system-ui, "Segoe UI", sans-serif |
--k-font-body |
Everything else. | system-ui, "Segoe UI", sans-serif |
--k-font-mono |
Code, kbd, kickers, eyebrows, the brand mark. | ui-monospace, Consolas, "Cascadia Mono", monospace |
The design page ships twelve ready pairings for these tokens. Six stay on system stacks — Grotesk, Editorial, Humanist, Console, Rounded, Print — zero requests, rendered with whatever the OS has. Six are real typefaces self-hosted with this site as woff2 (SIL OFL licensed, ~300 KB total, lazy-loaded per face): Inter, Manrope, Space Grotesk, Playfair Display, Lora, and JetBrains Mono — identical on every OS. The framework itself ships no fonts; the export tells you which woff2 files to copy, or gives a Google Fonts link if you'd rather link than host.
Leading & rhythm
Two tokens set the vertical feel: --k-leading
(1.65) is the body line-height, and
--k-leading-tight (1.12) is the heading
line-height. Vertical rhythm between blocks is automatic — a heading
that follows content gets room to breathe with no classes; the
base page documents that smart content
spacing.
Type in context
Specimens prove the tokens; compositions prove the system. Three small pieces of real typographic work, each built from nothing but the scale, the color steps, and the mono token.
The lede
An article opener: the heading does the display work, then
one paragraph steps up to k-text-l and softens to
k-text-soft — bigger but quieter, so it reads as
an invitation rather than a second heading.
The harbor rebuild, one year on
Twelve months after the storm, the shipwrights have a working slipway, a new saw shed, and a waiting list — this is how they did it.
Markup
<h3>The harbor rebuild, one year on</h3>
<p class="k-text-l k-text-soft">Twelve months after the storm, …</p>
The stat callout
A big number with a small caption:
.k-stat__value takes the display font and weight,
.k-stat__label drops to the xs step, faint and
uppercase. There's no wrapper class — any element holds a pair,
and a .k-cluster rows several up.
Markup
<div class="k-cluster">
<div><span class="k-stat__value">31</span><span class="k-stat__label">hulls restored</span></div>
<div><span class="k-stat__value">4.2t</span><span class="k-stat__label">oak reclaimed</span></div>
</div>
The meta row
The dense caption under a title — byline, date, reading
time. Everything drops to k-text-xs and fades to
k-text-faint; the machine-readable parts
(time) take k-mono so figures line
up and read as data.
Steam-bending white oak, safely
By R. Daiven · · 6 min read · in Workshop notes
Markup
<h4>Steam-bending white oak, safely</h4>
<p class="k-text-xs k-text-faint">By R. Daiven
· <time class="k-mono" datetime="2026-07-12">2026-07-12</time>
· 6 min read · in <a href="…">Workshop notes</a></p>
Text utilities
For one-off adjustments, the utilities
page documents the Text group — k-text-* for size,
color, and alignment, plus k-bold, k-mono,
k-uppercase, k-truncate, and
k-balance — every value a token, nothing arbitrary.