Skip to main content

max / makenotwork

Record the type scale in the design system charter
Author: Max Johnson <me@maxj.phd> · 2026-08-02 21:18 UTC
Signed with PGP, not checked
Commit: 318ba64ae4eb6b1dd4b792e2496f4ed74e672c13
Parent: 83b1c4b
1 file changed, +43 insertions, -7 deletions
@@ -11,16 +11,20 @@
11 11 | Tier | Token group | Where defined |
12 12 |---|---|---|
13 13 | Color | `--background`, `--detail`, `--highlight`, `--light-background`, surface family, `--text-muted`, `--border`, semantic (`--success/-bg`, `--warning/-bg/-border`, `--danger/-bg`, `--error/-bg`), `--stripe`, health (`--health-ok/-warn/-error/-unknown`), diff (`--diff-add/-bg`, `--diff-del/-bg`), `--focus-ring`, `--highlight-faint`, `--overlay` | `style.css:40-95` |
14 - | Type | `--font-heading` (Young Serif), `--font-mono` (IBM Plex Mono), `--font-body` (Lato) | `style.css:42-44` |
14 + | Typeface | `--font-heading` (Young Serif), `--font-mono` (IBM Plex Mono), `--font-body` (Lato) | `style.css:42-44` |
15 + | Type size | `--text-fine` / `--text-note` / `--text-body` / `--text-lead` / `--text-subhead` / `--text-head` / `--text-title` / `--text-display` / `--text-hero`, named for what the text is, not the size | `geometry.css` (generated by makeover-build) |
15 16 | Spacing | `--gap-bound` / `--gap-peer` / `--gap-group` / `--gap-section` / `--gap-pane` / `--gap-page`, named for the relationship they express, not the size | `geometry.css` (generated by makeover-build) |
16 17 | Radius | `--radius-sm` (4px, controls: buttons, inputs, chips, badges), `--radius-md` (0, containers: cards, panels, dropdowns), `--radius-round` (50%) | `style.css:216-222` |
17 18 | Shadow | Everyday depth: `--shadow-raised` (buttons, tabs), `--shadow-card` (cards, panels), `--shadow-inset` (inputs, recessed). All three alias the bevel pair `--bevel-raised` / `--bevel-inset` from `layout.css`, which makeover derives from each theme's own raised surface. Blurred (true elevation, floats over page): `--shadow-1` (subtle), `--shadow-2` (raised), `--shadow-3` (modal/overlay). | `style.css:224-243` |
18 19
19 20 Pure `#000` and `#fff` are forbidden outside the token table. Bootstrap-derived yellows (`#fff3cd`, `#ffc107`) are forbidden. Use `--warning-bg` / `--warning-border`.
20 21
21 - ## Type tiers (from `brand.md`)
22 + ## Type
22 23
23 - Every text element maps to exactly one tier:
24 + Two independent choices, and every text element makes both: which typeface, and
25 + which size.
26 +
27 + ### Typeface tiers (from `brand.md`)
24 28
25 29 - **H1 / wordmark / section heads**: `--font-heading` (Young Serif), `normal` weight, color `--detail`.
26 30 - **H2 / H3 / meta / taglines / footer**: `--font-mono` (IBM Plex Mono), `normal` weight.
@@ -28,6 +32,37 @@
28 32
29 33 No fourth typeface. No `font-family` declarations in templates.
30 34
35 + ### Size tiers
36 +
37 + Nine, generated from makeover-geometry as ratios of `--geometry-base`, so every
38 + size tracks the reader's own root font size. Named for what the text is, on the
39 + same argument as `--gap-*`: whether a caption should be 13px or 14px cannot be
40 + reviewed, whether a piece of text is a caption can.
41 +
42 + | Token | What it is | At the default base |
43 + |---|---|---|
44 + | `--text-fine` | timestamps, badges, legal lines | 12px |
45 + | `--text-note` | metadata, table cells, captions, form help | 14px |
46 + | `--text-body` | running copy | 16px |
47 + | `--text-lead` | emphasised copy, card titles | 18px |
48 + | `--text-subhead` | h3 | 20px |
49 + | `--text-head` | h2, section heads | 24px |
50 + | `--text-title` | h1, page titles | 32px |
51 + | `--text-display` | display copy | 40px |
52 + | `--text-hero` | a landing hero, at most one per page | 48px |
53 +
54 + Nothing below `--text-fine`. A size under 12px is a legibility problem rather
55 + than a tier; text that should recede does it with colour or weight.
56 +
57 + Sizes do not shift under a coarse pointer, unlike spacing. Density is a claim
58 + about the contact patch and text is not a tap target, so a phone gets the same
59 + tiers a desktop does and the reader's root size is the knob.
60 +
61 + The `em` unit is banned for font sizes: it compounds against whatever the parent
62 + happens to be, so the same rule renders at different sizes depending on where it
63 + is used. Where text needs to sit one rung below its container (inline `code`
64 + inside long-form copy), name the rung.
65 +
31 66 ## Components: canonical primitive table
32 67
33 68 For each primitive, exactly one canonical class **or** one canonical partial. Variants are class modifiers; nothing else.
@@ -141,10 +176,11 @@
141 176 6. **Empty / error / loading states use the shared partial.** Never assemble these inline.
142 177 7. **Destructive actions** use `.danger` button class plus the `confirm_dialog` macro. No bare destructive buttons.
143 178 8. **Spacing values come from `--gap-*` tokens.** Pick the one that names what the rule separates: `bound` inside a thing, `peer` between siblings, `group` between related controls, `section` between parts of a pane, `pane` between panes, `page` at page level. Raw values and off-scale values are bugs. The tokens are generated from makeover-geometry and shift under a coarse pointer, so a hardcoded value silently opts that rule out of touch density.
144 - 9. **Page-scoped CSS is a last resort.** Default to composing layout + component primitives. Page-scoped sections exist for genuinely page-specific layout (grids, long-form typography, marketing heroes), not for shapes that are cards or list rows in disguise.
145 - 10. **No bare `<h1>` / `<h2>` in templates.** Pick a heading class: `.brand-h1` (wordmark), `.page-title` (page h1), `.subtitle-h2` (page subtitle on auth/wizards), `.subsection-title` (default h2 inside dashboards/tabs/partials), or `.section-header` (prose sub-section with bottom border). Bare tags inside server-rendered markdown / user content (e.g. `partials/item_text_editor.html` JS preview) are the only exception.
146 - 11. **Button class is `.btn-primary` / `.btn-secondary` / `.btn-danger`.** Works on both `<button>` and `<a>` (the `<a><button>` antipattern is banned. Use `<a class="btn-primary">…</a>`). The bare `button.primary` shorthand has been retired.
147 - 12. **Class names use kebab-case only.** No BEM `__` separator (retired 2026-05-20, was 112 sites across 20 prefixes, now flattened to single-dash). Modifier `--` (e.g. `.card--bordered`) is still allowed.
179 + 9. **Font sizes come from `--text-*` tokens.** Pick the one that names what the text is: `fine` for timestamps and badges, `note` for metadata and table cells, `body` for running copy, `lead` for emphasised copy and card titles, `subhead` / `head` / `title` for the three heading levels, `display` and `hero` above the hierarchy. Raw values and off-scale values are bugs, including a value that differs from a tier by a fraction of a pixel. Nothing sits below `fine`: text that should recede does it with colour or weight.
180 + 10. **Page-scoped CSS is a last resort.** Default to composing layout + component primitives. Page-scoped sections exist for genuinely page-specific layout (grids, long-form typography, marketing heroes), not for shapes that are cards or list rows in disguise.
181 + 11. **No bare `<h1>` / `<h2>` in templates.** Pick a heading class: `.brand-h1` (wordmark), `.page-title` (page h1), `.subtitle-h2` (page subtitle on auth/wizards), `.subsection-title` (default h2 inside dashboards/tabs/partials), or `.section-header` (prose sub-section with bottom border). Bare tags inside server-rendered markdown / user content (e.g. `partials/item_text_editor.html` JS preview) are the only exception.
182 + 12. **Button class is `.btn-primary` / `.btn-secondary` / `.btn-danger`.** Works on both `<button>` and `<a>` (the `<a><button>` antipattern is banned. Use `<a class="btn-primary">…</a>`). The bare `button.primary` shorthand has been retired.
183 + 13. **Class names use kebab-case only.** No BEM `__` separator (retired 2026-05-20, was 112 sites across 20 prefixes, now flattened to single-dash). Modifier `--` (e.g. `.card--bordered`) is still allowed.
148 184
149 185 ## How to extend
150 186