Repository navigation
Typography
This page sets the typefaces, sizes and text styles of the website, and how numbers are set. Use it when styling text; the values come from Design Tokens, and how to write times, dates and durations is on Writing Style.
One family for all text, one for code.
| Instrument Sans | JetBrains Mono | |
|---|---|---|
| Used for | Body, headings, navigation, buttons, the wordmark | Code blocks, inline code, terminal commands |
| Licence | SIL OFL 1.1, © 2022 The Instrument Sans Project Authors | SIL OFL 1.1, © 2020 The JetBrains Mono Project Authors |
| Variable axes | Weight 400–700, width 75–100 | Weight 100–800 |
| Weights used | 400, 500, 600 | 400, 500 |
| Width used | 100 (normal); narrower only after a mockup is approved | — |
| Specimen | Google Fonts | Google Fonts |
Axes are from Next.js's Google font data (next 16.4.3). The Open Font License allows using, embedding and redistributing both fonts with the licence, commercial use included; they may not be sold on their own.
Fonts load through next/font/google, which downloads them at build time and serves them from the site: "No requests are sent to Google by the browser" (Next.js: Font).
// website/app/layout.tsx
import { Instrument_Sans, JetBrains_Mono } from 'next/font/google';
import type { ReactNode } from 'react';
import { Provider } from '@/components/provider';
import './global.css';
const sans = Instrument_Sans({
subsets: ['latin'],
display: 'swap',
variable: '--font-instrument-sans',
});
const mono = JetBrains_Mono({
subsets: ['latin'],
display: 'swap',
variable: '--font-jetbrains-mono',
});
export default function RootLayout({ children }: { children: ReactNode }) {
return (
<html lang="en" className={`${sans.variable} ${mono.variable}`} suppressHydrationWarning>
<body className="flex min-h-screen flex-col font-sans">
<Provider>{children}</Provider>
</body>
</html>
);
}Provider (the RootProvider with search, theme={{ hotKey: false }} and the icon stroke) is on Website Tech Stack.
| Option | Value | Why |
|---|---|---|
subsets |
['latin'] |
Covers English, the en dash, the minus sign and ×. Without a subset, Next.js warns when preload is on. |
display |
'swap' |
Text shows at once in the fallback, then swaps. Also the default; written out so nobody changes it. |
variable |
--font-instrument-sans, --font-jetbrains-mono
|
Mapped to Tailwind's --font-sans and --font-mono in @theme inline (Design Tokens). |
weight, axes
|
not set | Variable fonts: one file covers every weight. Add axes: ['wdth'] only if a narrower width is approved. |
adjustFontFallback |
default true
|
Sizes a local fallback to match, reducing layout shift. |
Social cards drawn with next/og need a TTF instead; see Website Tech Stack.
Checked against the font files on 2026-10-24.
| Character | Where | In Instrument Sans? | Rule |
|---|---|---|---|
| µ (U+00B5) | "8.5 µs" | No; drawn from the fallback | Accept it. JetBrains Mono has it. |
| ½ (U+00BD) | README "5½-hour span" | No | On the site, write "5.5-hour". |
| → (U+2192) | Arrows in UI text | In the font, not in the latin subset | Use a lucide-react icon, or write "to". |
| − (U+2212), – (U+2013), × (U+00D7), no-break space (U+00A0) | Negatives, ranges, speed-ups, "8.5 µs" | Yes | Use freely. Not the narrow no-break space (U+202F), which the font lacks. |
Sizes in px, rem at 16 px. Each role is a Tailwind utility defined on Design Tokens that sets size, line height, and for headings weight and tracking.
| Role | Utility | Size | Line height | Weight | Tracking | Used for |
|---|---|---|---|---|---|---|
| Caption | text-caption |
12 | 1.5 | 400 | 0 | Captions, table notes, badges |
| Code | text-code |
13 | 1.6 | 400 | 0 | Code blocks |
| UI | text-ui |
14 | 1.5 | 400; 500 when active | 0 | Sidebar, TOC, tabs, buttons, inputs, tables |
| Body | text-body |
16 | 1.7 | 400 | 0 | Docs prose |
| Lead | text-lead |
18 | 1.6 | 400 | 0 | Proposed: page description, landing intro |
| H3 | text-h3 |
20 | 1.3 | 600 | −0.01em | Third-level headings |
| H2 | text-h2 |
24 | 1.25 | 600 | −0.02em | Second-level headings |
| Section | text-section |
30 | 1.25 | 600 | −0.02em | Proposed: landing section headings; docs H1 below 640 px |
| H1 | text-h1 |
38 | 1.1 | 600 | −0.03em | Page title |
| Display, phone | text-display-sm |
38 | 1.1 | 600 | −0.035em | Landing headline below 768 px |
| Display, tablet | text-display-md |
48 | 1.1 | 600 | −0.035em | Proposed: landing headline 768–1023 px |
| Display | text-display |
60 | 1.1 (Proposed) | 600 | −0.035em | Landing headline from 1024 px |
Proposed: H4, when needed, is Body size, weight 600, line height 1.5.
| Style | Rules |
|---|---|
| Headings | 600, text, sentence case, tracking as in the scale. Proposed: space above a heading twice the space below. |
| Body | 16 px / 1.7, text, 400. Bold is 600. Links: Color. Proposed: 16 px between paragraphs. |
| Code blocks | JetBrains Mono 13 px / 1.6 on surface, 16 px side padding. Keep sample lines to 77 characters or fewer: that fills the 640 px column (608 px after padding, 7.8 px per character at 13 px). Longer lines scroll inside the block. |
| Inline code |
Proposed: JetBrains Mono at 0.875 em, surface background, 4 px radius, 2 px side padding. Never in headings. |
| UI | 14 px / 1.5. Navigation and inactive tabs text-2; active items text, 500. Button labels 500. |
| Captions | 12 px / 1.5, text-2 (text-3 only on bg and raised). |
Fumadocs sizes its title and prose headings itself (the title is text-[1.75em] in fumadocs-ui 16.17.8). These un-layered rules bring them onto our scale:
/* website/app/global.css — typography */
#nd-page h1 {
font-size: var(--text-h1);
line-height: 1.1;
letter-spacing: -0.03em;
font-weight: 600;
}
.prose h2 {
font-size: var(--text-h2);
line-height: 1.25;
letter-spacing: -0.02em;
font-weight: 600;
}
.prose h3 {
font-size: var(--text-h3);
line-height: 1.3;
letter-spacing: -0.01em;
font-weight: 600;
}
.prose {
font-size: var(--text-body);
line-height: 1.7;
}
@media (max-width: 639.98px) {
#nd-page h1 {
font-size: var(--text-section);
line-height: 1.25;
}
}
table,
time,
.tabular {
font-variant-numeric: tabular-nums;
}Instrument Sans has proportional digits by default ("1" is 391 units wide, "0" is 666), so columns of times would not line up. Its tnum feature gives equal-width digits, and the latin file Google serves keeps it (checked 2026-10-24).
Use tabular-nums on every time, date and duration, every benchmark figure, every numeric table column and the whole demo. Numeric columns are right-aligned with one unit per column. JetBrains Mono digits are already equal width.
| Text | Width | Characters per line |
|---|---|---|
| Docs prose | 640 px at 16 px | About 85 (Instrument Sans averages 0.47 em per character on README text) |
| Code | 640 px at 13 px, minus 32 px padding | 77 |
| Landing paragraphs | Proposed: 600 px | About 80 |
85 is above the 80 that WCAG 1.4.8 sets at AAA; the target is AA, so this is not a failure.
No all-caps: no uppercase, no text-transform, no small caps, including sidebar group titles, table headers and buttons. Sentence case everywhere. The product name is always lowercase "workclock" (Brand).
- Proposed roles and styles: Lead, Section, Display tablet size, Display line height, H4, heading and paragraph spacing, inline code, 600 px landing paragraphs.
-
Proposed: load Instrument Sans italic (
style: ['normal', 'italic']) if docs pages use emphasis, after comparing font file sizes.
workclock is MIT licensed. The repository documentation is authoritative; this wiki summarises it.