Color
The palette is ink-led: black type on white surfaces, hairline borders, and one loud color —
Miltinson Amber — used as a signal, never a fill. Every value on this page is read live from
@elirobinson/tokens/tokens.css; the swatches and contrast ratios below cannot drift from what
the package ships.
How to think about it
- Ink does the work. Headings are pure black, body text near-black, and hierarchy comes from the ink scale — not from adding color.
- Amber is the only loud color. Primary CTAs, the dot in the wordmark, eyebrow underlines, key stats, link hovers. If two things on a screen are amber, one of them is wrong.
- Forest is the quiet second. Trust marks, success states, and the earnest surfaces (coaching guides). It anchors; it doesn't shout.
- No gradients, no purple. Flat color only.
Reference semantic tokens (--fg, --surface, --accent) in components, never raw
scale values (--ink-500) — the semantic layer is what makes a future dark theme
possible without touching component code.
Ink — the neutral scale
| Token | Value | Hex | On white | On black | |
|---|---|---|---|---|---|
--ink-0 | #ffffff | #ffffff | 1.00:1 | 21.00:1 | |
--ink-50 | oklch(98.4% 0.002 247) | #f9fafb | 1.05:1 | 20.06:1 | |
--ink-100 | oklch(96.2% 0.003 247) | #f1f3f4 | 1.12:1 | 18.81:1 | |
--ink-200 | oklch(92.8% 0.004 247) | #e5e7ea | 1.24:1 | 16.99:1 | |
--ink-300 | oklch(86% 0.005 247) | #ced1d4 | 1.53:1 | 13.73:1 | |
--ink-400 | oklch(70% 0.006 247) | #9b9fa2 | 2.67:1 | 7.87:1 | |
--ink-500 | oklch(55% 0.008 247) | #6e7276 | 4.85:1 | 4.33:1 | |
--ink-600 | oklch(42% 0.01 247) | #494e52 | 8.45:1 | 2.49:1 | |
--ink-700 | oklch(30% 0.01 247) | #2a2e33 | 13.62:1 | 1.54:1 | |
--ink-800 | oklch(20% 0.01 247) | #12171a | 18.09:1 | 1.16:1 | |
--ink-900 | oklch(12% 0.008 247) | #040608 | 20.29:1 | 1.03:1 | |
--ink-950 | oklch(6% 0.006 247) | #000101 | 20.91:1 | 1.00:1 | |
--ink-1000 | #000000 | #000000 | 21.00:1 | 1.00:1 |
Signal — Miltinson Amber
Warm, craftsman, honest. --signal-500 is the brand amber; the darker steps exist
because raw amber fails AA on white — text uses --signal-700 and darker.
| Token | Value | Hex | On white | On black | |
|---|---|---|---|---|---|
--signal-50 | oklch(97.5% 0.025 75) | #fff5e5 | 1.08:1 | 19.42:1 | |
--signal-100 | oklch(94% 0.055 75) | #ffe7c3 | 1.20:1 | 17.44:1 | |
--signal-200 | oklch(89% 0.1 75) | #ffd28f | 1.42:1 | 14.84:1 | |
--signal-300 | oklch(83% 0.14 72) | #ffb755 | 1.72:1 | 12.18:1 | |
--signal-400 | oklch(78% 0.16 68) | #f9a129 | 2.06:1 | 10.17:1 | |
--signal-500 | oklch(72.5% 0.175 65) | #ef8a00 | 2.53:1 | 8.30:1 | |
--signal-600 | oklch(63% 0.165 55) | #d26700 | 3.69:1 | 5.69:1 | |
--signal-700 | oklch(52% 0.145 45) | #a94608 | 5.86:1 | 3.59:1 | |
--signal-800 | oklch(40% 0.115 40) | #782c0c | 9.69:1 | 2.17:1 | |
--signal-900 | oklch(28% 0.08 38) | #481707 | 15.00:1 | 1.40:1 |
Anchor — Miltinson Forest
| Token | Value | Hex | On white | On black | |
|---|---|---|---|---|---|
--anchor-50 | oklch(96% 0.018 165) | #e7f6ef | 1.12:1 | 18.82:1 | |
--anchor-100 | oklch(91% 0.035 165) | #cde9db | 1.29:1 | 16.29:1 | |
--anchor-200 | oklch(82% 0.055 162) | #a5d0b9 | 1.71:1 | 12.30:1 | |
--anchor-300 | oklch(70% 0.075 160) | #74ad8e | 2.58:1 | 8.13:1 | |
--anchor-400 | oklch(55% 0.085 158) | #42815e | 4.65:1 | 4.51:1 | |
--anchor-500 | oklch(42% 0.08 156) | #215a3a | 8.13:1 | 2.58:1 | |
--anchor-600 | oklch(34% 0.065 156) | #164229 | 11.41:1 | 1.84:1 | |
--anchor-700 | oklch(26% 0.05 156) | #0b2b1a | 15.26:1 | 1.38:1 | |
--anchor-800 | oklch(19% 0.035 156) | #05180d | 18.32:1 | 1.15:1 | |
--anchor-900 | oklch(13% 0.025 156) | #010a04 | 20.06:1 | 1.05:1 | |
--anchor-hover | oklch(55% 0.085 158) | #42815e | 4.65:1 | 4.51:1 | |
--anchor-press | oklch(34% 0.065 156) | #164229 | 11.41:1 | 1.84:1 | |
--anchor-fg | #ffffff | #ffffff | 1.00:1 | 21.00:1 | |
--anchor-tint | oklch(96% 0.018 165) | #e7f6ef | 1.12:1 | 18.82:1 |
Semantic tokens
These are the names components actually use. Each references a base-scale value, so retuning the palette is a one-line change per token.
Backgrounds and surfaces
| Token | References | Purpose | |
|---|---|---|---|
--bg | var(--ink-0) | page background | |
--bg-subtle | var(--ink-50) | zebra / panel | |
--bg-muted | var(--ink-100) | code blocks, inset wells | |
--bg-inverse | var(--ink-1000) | dark hero, footer | |
--surface | var(--ink-0) | cards | |
--surface-2 | var(--ink-50) | nested cards | |
--surface-3 | var(--ink-100) | deeper inset |
Text
| Token | References | Purpose | |
|---|---|---|---|
--fg | var(--ink-1000) | primary text | |
--fg-1 | var(--ink-1000) | alias | |
--fg-2 | var(--ink-600) | secondary text | |
--fg-3 | var(--ink-500) | tertiary / meta | |
--fg-4 | var(--ink-400) | disabled | |
--fg-inverse | var(--ink-0) | on dark | |
--fg-on-signal | var(--ink-1000) | text on lime |
Borders
| Token | References | Purpose | |
|---|---|---|---|
--border | var(--ink-200) | default hairline | |
--border-strong | var(--ink-300) | emphasized | |
--border-inverse | oklch(100% 0 0 / 0.12) | on dark |
Accent — Miltinson Amber
| Token | References | Purpose | |
|---|---|---|---|
--accent | var(--signal-500) | Miltinson Amber | |
--accent-hover | var(--signal-400) | — | |
--accent-press | var(--signal-600) | — | |
--accent-fg | var(--ink-1000) | — | |
--accent-tint | var(--signal-50) | very-light amber backgrounds |
Anchor — Miltinson Forest
| Token | References | Purpose | |
|---|---|---|---|
--anchor | var(--anchor-500) | Miltinson Forest | |
--anchor-hover | var(--anchor-400) | — | |
--anchor-press | var(--anchor-600) | — | |
--anchor-fg | var(--ink-0) | — | |
--anchor-tint | var(--anchor-50) | — |
Links and focus
| Token | References | Purpose | |
|---|---|---|---|
--focus-ring | var(--ink-1000) | high-contrast focus, ink-on-bg | |
--link | var(--ink-1000) | — | |
--link-hover | var(--signal-700) | — | |
--link-visited | var(--ink-700) | — |
Status
| Token | References | Purpose | |
|---|---|---|---|
--status-success | var(--anchor-500) | — | |
--status-warning | var(--signal-600) | — | |
--status-danger | oklch(58% 0.22 25) | — | |
--status-info | oklch(60% 0.13 240) | — |
Contrast
Computed from the live token values with WCAG 2.x math. Two pairings deserve a note:
raw amber (--accent) fails AA as text on white — which is exactly why button labels
use --accent-fg (ink on amber, 8.7:1) and text links only shift to amber's dark step
(--signal-700) on hover. --status-warning passes at large sizes only; pair it with
an icon or keep warning text at --fg with a warning-colored border.
| Foreground | Background | Sample | Ratio | WCAG AA |
|---|---|---|---|---|
--fgBody text on the page | --bg | Aa 16px | 21.00:1 | AA |
--fg-2Secondary text | --bg | Aa 16px | 8.45:1 | AA |
--fg-3Tertiary / meta text | --bg | Aa 16px | 4.85:1 | AA |
--fgText on panels and zebra rows | --bg-subtle | Aa 16px | 20.06:1 | AA |
--accent-fgLabel on an accent (amber) button | --accent | Aa 16px | 8.30:1 | AA |
--anchor-fgLabel on a forest button | --anchor | Aa 16px | 8.13:1 | AA |
--fg-inverseText on dark hero / footer | --bg-inverse | Aa 16px | 21.00:1 | AA |
--accentAmber accents on ink (stats, links on dark) | --bg-inverse | Aa 16px | 8.30:1 | AA |
--link-hoverLink hover color | --bg | Aa 16px | 5.86:1 | AA |
--status-successSuccess text | --bg | Aa 16px | 8.13:1 | AA |
--status-warningWarning text | --bg | Aa 16px | 3.69:1 | AA large only |
--status-dangerDanger text | --bg | Aa 16px | 4.79:1 | AA |
--status-infoInfo text | --bg | Aa 16px | 3.88:1 | AA large only |