Color
The palette is ink-led: black type on white surfaces, hairline borders, and one loud color,
Miltinson Amber. Every value on this page is read live from the
token stylesheets @elirobinson/tokens ships — tokens.css and the palettes.css it imports —
so the swatches and contrast ratios below cannot drift from what the package ships. They are the
default palette in its light theme; the palette is a data-palette attribute on the root element,
and nothing on this page is hardcoded per brand.
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 and the earnest surfaces (coaching guides). It is no longer what success is made of — status has its own four hues, which stay put when the palette changes.
- 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% calc(0.002 * 1) 247) | — | — | — | |
--ink-100 | oklch(96.2% calc(0.003 * 1) 247) | — | — | — | |
--ink-200 | oklch(92.8% calc(0.004 * 1) 247) | — | — | — | |
--ink-300 | oklch(86% calc(0.005 * 1) 247) | — | — | — | |
--ink-400 | oklch(70% calc(0.006 * 1) 247) | — | — | — | |
--ink-500 | oklch(55% calc(0.008 * 1) 247) | — | — | — | |
--ink-600 | oklch(42% calc(0.01 * 1) 247) | — | — | — | |
--ink-700 | oklch(30% calc(0.01 * 1) 247) | — | — | — | |
--ink-800 | oklch(20% calc(0.01 * 1) 247) | — | — | — | |
--ink-900 | oklch(12% calc(0.008 * 1) 247) | — | — | — | |
--ink-950 | oklch(6% calc(0.006 * 1) 247) | — | — | — | |
--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 — anything amber a user has to read uses --accent-ink,
which the default palette points at --signal-800.
| 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 | |
--anchor-ink | oklch(34% 0.065 156) | #164229 | 11.41:1 | 1.84: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) | decorative / non-informational grey — 2.67:1 | |
--fg-disabled | var(--ink-500) | 4.85:1 — disabled control text | |
--fg-inverse | var(--ink-0) | on dark | |
--fg-inverse-2 | var(--ink-300) | secondary on --bg-inverse — 13.73:1 | |
--fg-inverse-3 | var(--ink-400) | tertiary / meta on --bg-inverse — 7.87:1 | |
--fg-on-signal | var(--accent-fg) | — |
Borders
| Token | References | Purpose | |
|---|---|---|---|
--border | var(--ink-200) | decorative hairline — 1.24:1 | |
--border-strong | var(--ink-300) | decorative, emphasized — 1.53:1 | |
--border-inverse | oklch(100% 0 0 / 0.12) | on dark | |
--border-control | oklch(62% calc(0.008 * var(--n-mult)) var(--n-h)) | 3.64:1 on --bg — control edges, SC 1.4.11 |
Accent — the palette’s signal color
| Token | References | Purpose | |
|---|---|---|---|
--accent | var(--signal-500) | fill — 2.53:1, fill-only | |
--accent-hover | var(--signal-400) | fill — 2.06:1, fill-only | |
--accent-press | var(--signal-600) | fill — 3.69:1 | |
--accent-fg | var(--ink-1000) | on the three fills: 8.30 / 10.17 / 5.69 | |
--accent-tint | var(--signal-50) | with --accent-ink: 8.96:1 | |
--accent-ink | var(--signal-800) | 9.69:1 | |
--accent-ink-inverse | var(--signal-400) | on --bg-inverse — 10.17:1 |
Anchor — the palette’s second color
| Token | References | Purpose | |
|---|---|---|---|
--anchor | var(--anchor-500) | fill — 8.13:1 | |
--anchor-hover | var(--anchor-400) | fill — 4.65:1 | |
--anchor-press | var(--anchor-600) | fill — 11.41:1 | |
--anchor-fg | var(--ink-0) | on the three fills: 8.13 / 4.65 / 11.41 | |
--anchor-tint | var(--anchor-50) | with --anchor-ink: 10.23:1 | |
--anchor-ink | var(--anchor-600) | 11.41:1 |
Links and focus
| Token | References | Purpose | |
|---|---|---|---|
--focus-ring | var(--ink-1000) | 21:1 — high-contrast focus, ink-on-bg | |
--link | var(--ink-1000) | 21:1 | |
--link-hover | var(--signal-800) | 9.69:1 — was --signal-700 at 5.86:1 | |
--link-visited | var(--ink-700) | 13.62:1 | |
--link-on-fill | currentColor | — |
Status
| Token | References | Purpose | |
|---|---|---|---|
--status-success | oklch(51.9% 0.145 150) | fill — 5.17:1 light, 4.06:1 dark | |
--status-success-on | var(--ink-0) | on the fill — 5.17:1 | |
--status-success-fg | oklch(44% 0.12 150) | 7.35:1 on --bg | |
--status-success-tint | oklch(96.5% 0.03 150) | with -fg: 6.71:1 | |
--status-success-tint-edge | oklch(88% 0.06 150) | decorative hairline on the tint | |
--status-warning | oklch(80.25% 0.16 85) | fill — 1.87:1 light, 11.22:1 dark — must be edged | |
--status-warning-border | oklch(61.9% 0.14 70) | 3.76:1 — the edge that carries SC 1.4.11 | |
--status-warning-on | var(--ink-1000) | on the fill — 11.22:1 | |
--status-warning-fg | oklch(47% 0.105 72) | 6.97:1 on --bg | |
--status-warning-tint | oklch(96.5% 0.05 85) | with -fg: 6.24:1 | |
--status-warning-tint-edge | oklch(87% 0.1 85) | decorative hairline on the tint | |
--status-danger | oklch(54.85% 0.2 27) | fill — 5.41:1 light, 3.88:1 dark | |
--status-danger-on | var(--ink-0) | on the fill — 5.41:1 | |
--status-danger-fg | oklch(47% 0.19 27) | 7.54:1 on --bg | |
--status-danger-tint | oklch(96.5% 0.025 27) | with -fg: 6.69:1 | |
--status-danger-tint-edge | oklch(87% 0.07 27) | decorative hairline on the tint | |
--status-info | oklch(54% 0.155 250) | fill — 5.06:1 light, 4.15:1 dark | |
--status-info-on | var(--ink-0) | on the fill — 5.06:1 | |
--status-info-fg | oklch(46% 0.14 250) | 7.09:1 on --bg | |
--status-info-tint | oklch(96.5% 0.03 250) | with -fg: 6.38:1 | |
--status-info-tint-edge | oklch(87% 0.07 250) | decorative hairline on the tint |
Charts — categorical, palette-independent
| Token | References | Purpose | |
|---|---|---|---|
--chart-1 | oklch(62% 0.13 25) | 3.87:1 | |
--chart-2 | oklch(62% 0.13 205) | 3.34:1 | |
--chart-3 | oklch(62% 0.13 115) | 3.55:1 | |
--chart-4 | oklch(62% 0.13 295) | 3.81:1 | |
--chart-5 | oklch(62% 0.13 70) | 3.74:1 | |
--chart-6 | oklch(62% 0.13 250) | 3.63:1 | |
--chart-7 | oklch(62% 0.13 160) | 3.42:1 | |
--chart-8 | oklch(62% 0.13 340) | 3.90:1 | |
--chart-grid | var(--border-strong) | decorative — 1.53:1 | |
--chart-axis | var(--border-control) | 3.64:1 — the axis is a meaningful line |
Overlays
| Token | References | Purpose | |
|---|---|---|---|
--scrim | oklch(0% 0 0 / 0.56) | — |
Contrast
Computed from the live token values with WCAG 2.x math. Two pairings deserve a note. Raw amber
(--accent) is a fill and fails AA as text on white — which is why button labels use
--accent-fg and why anything amber that has to be read uses --accent-ink. And
--status-warning is the documented exception: yellow cannot clear 3:1 on white and still read
as yellow, so a warning fill or rule must be edged with --status-warning-border, which carries
that 3:1 itself. Warning text is --status-warning-fg, never the fill.
| Foreground | Background | Sample | Ratio | WCAG AA |
|---|---|---|---|---|
--fgBody text on the page | --bg | Aa 16px | 21.00:1 | AA |
--fg-2Secondary text | --bg | Aa 16px | — | |
--fg-3Tertiary / meta text | --bg | Aa 16px | — | |
--fgText on panels and zebra rows | --bg-subtle | Aa 16px | — | |
--accent-fgLabel on the accent fill | --accent | Aa 16px | 8.30:1 | AA |
--anchor-fgLabel on the anchor fill | --anchor | Aa 16px | 8.13:1 | AA |
--fg-inverseText on dark hero / footer | --bg-inverse | Aa 16px | 21.00:1 | AA |
--accentAccent fill on ink (stats, links on dark) | --bg-inverse | Aa 16px | 8.30:1 | AA |
--accent-inkBrand color at readable weight (marks, glyphs) | --bg | Aa 16px | 9.69:1 | AA |
--link-hoverLink hover color | --bg | Aa 16px | 9.69:1 | AA |
--status-success-fgSuccess text | --bg | Aa 16px | 7.35:1 | AA |
--status-warning-fgWarning text | --bg | Aa 16px | 6.97:1 | AA |
--status-danger-fgDanger text | --bg | Aa 16px | 7.54:1 | AA |
--status-info-fgInfo text | --bg | Aa 16px | 7.09:1 | AA |
--status-success-onLabel on a success fill | --status-success | Aa 16px | 5.17:1 | AA |
--status-warning-onLabel on a warning fill | --status-warning | Aa 16px | 11.22:1 | AA |
--status-danger-onLabel on a danger fill | --status-danger | Aa 16px | 5.41:1 | AA |
--status-info-onLabel on an info fill | --status-info | Aa 16px | 5.06:1 | AA |
--status-warning-borderThe edge a warning fill must carry (non-text, 3:1) | --bg | Aa 16px | 3.76:1 | AA large only |