<!-- https://getkonjo.com/design/reference/tokens · source: .claude/skills/design/references/tokens.md -->

<!-- GENERATED by scripts/design/sync-tokens.mjs — do not edit by hand. -->
<!-- Change a value in packages/design-tokens/tokens.ts, then: npm run design:sync -->

# Konjo tokens — the complete tables

Every number and every ratio below is read straight out of
[`packages/design-tokens/tokens.ts`](../../../../packages/design-tokens/tokens.ts) and
recomputed on each run. If a value here disagrees with the code, the generator is stale —
run `npm run design:sync`. Never copy a value out of here into a component: import the
token. Colours reach components only through `useTheme()` / `createThemedStyles`.

**There is no "nearest existing token" fallback.** If the token you want is not in these
tables, it does not exist — say so and stop, rather than approximating. Approximating is
how a 48pt button ended up carrying 13pt type.

## Space

The ramp — 14 steps, and nothing between them:

| Step | Value |
|---|---|
| `spacing.s0` | 0 |
| `spacing.s2` | 2 |
| `spacing.s4` | 4 |
| `spacing.s6` | 6 |
| `spacing.s8` | 8 |
| `spacing.s12` | 12 |
| `spacing.s16` | 16 |
| `spacing.s20` | 20 |
| `spacing.s24` | 24 |
| `spacing.s32` | 32 |
| `spacing.s40` | 40 |
| `spacing.s48` | 48 |
| `spacing.s64` | 64 |
| `spacing.s80` | 80 |

Aliases (the same numbers, named for where they go):

| Alias | Value |
|---|---|
| `spacing.page` | 20 |
| `spacing.section` | 24 |
| `spacing.card` | 16 |
| `spacing.pageHorizontal` | 20 |
| `spacing.detailHorizontal` | 24 |
| `spacing.sectionGap` | 24 |
| `spacing.componentPadding` | 16 |
| `spacing.tight` | 8 |
| `spacing.stickyTop` | 16 |
| `spacing.stickyBottom` | 28 |

## Type

| Token | Size | Weight | Line height | Transform | Letter spacing |
|---|---|---|---|---|---|
| `typography.pageTitle` | 28 | 500 | **none** | — | — |
| `typography.sessionTitle` | 28 | 500 | **none** | — | — |
| `typography.headerTitle` | 15 | 500 | **none** | — | — |
| `typography.sectionLabel` | 12 | 500 | **none** | uppercase | 0.5 |
| `typography.body` | 15 | 400 | 22 | — | — |
| `typography.bodyLarge` | 16 | 400 | 24 | — | — |
| `typography.buttonLarge` | 17 | 500 | **none** | — | — |
| `typography.buttonMedium` | 16 | 500 | **none** | — | — |
| `typography.tagChip` | 13 | 400 | **none** | — | — |
| `typography.caption` | 13 | 400 | 18 | — | — |
| `typography.rowTitle` | 16 | 600 | 22 | — | — |
| `typography.tabLabelActive` | 10 | 500 | **none** | — | — |
| `typography.tabLabelInactive` | 10 | 400 | **none** | — | — |
| `typography.displayTitle` | 28 | 800 | **none** | — | -0.5 |
| `typography.wordmark` | 40 | 400 | **none** | — | 2 |
| `typography.eyebrow` | 11 | 800 | 14 | uppercase | 1.2 |
| `typography.bandLabel` | 16 | 900 | **none** | uppercase | 0.8 |
| `typography.bandLabelLead` | 18 | 900 | **none** | uppercase | 0.8 |
| `typography.bandSub` | 12 | 600 | **none** | — | — |
| `typography.hero` | 64 | 900 | 62 | — | 0 |
| `typography.statement` | 46 | 900 | 46 | uppercase | 0 |
| `typography.metric` | 28 | 800 | 32 | — | 0 |
| `typography.display` | 34 | 800 | 38 | — | 0 |
| `typography.title` | 22 | 700 | 28 | — | -0.3 |
| `typography.micro` | 12 | 500 | 16 | — | — |
| `typography.label` | 13 | 700 | 16 | — | 0.6 |

**14 entries still carry no line height** (`pageTitle`, `sessionTitle`, `headerTitle`, `sectionLabel`, `buttonLarge`, `buttonMedium`, `tagChip`, `tabLabelActive`, `tabLabelInactive`, `displayTitle`, `wordmark`, `bandLabel`, `bandLabelLead`, `bandSub`). Text in them reflows unpredictably at large Dynamic Type sizes; prefer an entry that has one.

## Colour

56 tokens. Light and dark are structurally paired —
`ThemeColors = typeof lightColors` makes a one-sided addition a compile error.

| Token | Light | Dark |
|---|---|---|
| `brand.red` | `#D62828` | `#D62828` |
| `brand.ink` | `#111111` | `#111111` |
| `brand.black` | `#111111` | `#111111` |
| `brand.yellow` | `#FFD60A` | `#FFD60A` |
| `brand.onRed` | `#FFFFFF` | `#FFFFFF` |
| `brand.redHover` | `#B91F1F` | `#B91F1F` |
| `brand.redActive` | `#A01B1B` | `#A01B1B` |
| `accent.red` | `#C42020` | `#FF5252` |
| `accent.onHero` | `#FF5252` | `#C42020` |
| `surface.white` | `#FFFFFF` | `#0E0E10` |
| `surface.muted` | `#FAFAFA` | `#16161A` |
| `surface.tag` | `#F4F4F4` | `#232327` |
| `surface.page` | `#F2F2F3` | `#0E0E10` |
| `surface.card` | `#FFFFFF` | `#1C1C22` |
| `surface.hero` | `#111111` | `#F2F2F2` |
| `surface.heroHover` | `#242424` | `#DFDFDF` |
| `surface.heroActive` | `#292929` | `#DADADA` |
| `text.primary` | `#1F1F1F` | `#F2F2F2` |
| `text.secondary` | `#6B6B6B` | `#A8A8AE` |
| `text.tertiary` | `#737373` | `#909094` |
| `text.quaternary` | `#BBBBBB` | `#5A5A60` |
| `text.onHero` | `#FFFFFF` | `#131313` |
| `text.onHeroMuted` | `#A8A8AE` | `#5A5A60` |
| `border.hairline` | `#E8E8E8` | `#2C2C2F` |
| `border.default` | `#E8E8E8` | `#2C2C2F` |
| `border.subtle` | `#E0E0E0` | `#232326` |
| `border.input` | `#D8D8D8` | `#3A3A40` |
| `border.control` | `#88888C` | `#6A6A72` |
| `border.focus` | `#1F1F1F` | `#F2F2F2` |
| `border.focusOnHero` | `#F2F2F2` | `#1F1F1F` |
| `overlay.dim` | `rgba(0,0,0,0.35)` | `rgba(0,0,0,0.5)` |
| `overlay.strong` | `rgba(0,0,0,0.6)` | `rgba(0,0,0,0.7)` |
| `overlay.onBrandSoft` | `rgba(255,255,255,0.15)` | `rgba(255,255,255,0.15)` |
| `overlay.onBrandMutedText` | `rgba(255,255,255,0.85)` | `rgba(255,255,255,0.85)` |
| `state.disabledFill` | `#444444` | `#3A3A3E` |
| `state.disabledAccent` | `#666666` | `#6A6A70` |
| `feedback.errorFill` | `#FDECEE` | `#3A1518` |
| `feedback.errorText` | `#B73232` | `#FF6B6B` |
| `feedback.errorBorder` | `#C26A76` | `#C26A76` |
| `feedback.warningFill` | `#FFF6E0` | `#332A0E` |
| `feedback.warningText` | `#8A6300` | `#E8C35A` |
| `feedback.warningBorder` | `#9C7718` | `#9C7718` |
| `feedback.infoFill` | `#EAF2FB` | `#13263A` |
| `feedback.infoText` | `#1F4F8A` | `#7FB3E8` |
| `feedback.infoBorder` | `#5881B0` | `#5881B0` |
| `feedback.successFill` | `#E6F4EC` | `#11301E` |
| `feedback.successText` | `#1A7A42` | `#5BC487` |
| `feedback.successBorder` | `#53956D` | `#53956D` |
| `band.lead` | `#131313` | `#F5F5F5` |
| `band.leadText` | `#FFFFFF` | `#131313` |
| `band.leadSub` | `rgba(255,255,255,0.7)` | `rgba(0,0,0,0.6)` |
| `band.steel` | `#ECECEC` | `#26262B` |
| `band.steelText` | `#131313` | `#F2F2F2` |
| `band.steelSub` | `#777777` | `#9A9AA0` |
| `gold.fill` | `#FFF4D6` | `#2A2310` |
| `gold.text` | `#8A6300` | `#E0A800` |

## Contrast — computed, both themes

Text against every surface it can legally sit on. **✓ clears 4.5:1 (SC 1.4.3);
✗ does not.** A ✗ is not a warning, it is a defect: pick a different token.

**Light**

| Text token | page | card | muted | tag | hero | errFill | warnFill | infoFill | okFill |
|---|---|---|---|---|---|---|---|---|---|
| `text.primary` | 14.73 ✓ | 16.48 ✓ | 15.79 ✓ | 14.98 ✓ | 1.14 ✗ | 14.44 ✓ | 15.31 ✓ | 14.59 ✓ | 14.53 ✓ |
| `text.secondary` | 4.76 ✓ | 5.32 ✓ | 5.10 ✓ | 4.84 ✓ | 3.54 ✗ | 4.67 ✓ | 4.95 ✓ | 4.71 ✓ | 4.69 ✓ |
| `text.tertiary` | 4.23 ✗ | 4.74 ✓ | 4.54 ✓ | 4.31 ✗ | 3.98 ✗ | 4.15 ✗ | 4.40 ✗ | 4.19 ✗ | 4.18 ✗ |
| `text.quaternary` | 1.71 ✗ | 1.91 ✗ | 1.83 ✗ | 1.74 ✗ | 9.83 ✓ | 1.68 ✗ | 1.78 ✗ | 1.69 ✗ | 1.69 ✗ |
| `text.onHero` | 1.11 ✗ | 1.00 ✗ | 1.04 ✗ | 1.09 ✗ | 18.88 ✓ | 1.14 ✗ | 1.07 ✗ | 1.12 ✗ | 1.13 ✗ |
| `text.onHeroMuted` | 2.11 ✗ | 2.36 ✗ | 2.26 ✗ | 2.15 ✗ | 7.98 ✓ | 2.07 ✗ | 2.19 ✗ | 2.09 ✗ | 2.08 ✗ |
| `accent.red` | 5.25 ✓ | 5.87 ✓ | 5.62 ✓ | 5.34 ✓ | 3.21 ✗ | 5.15 ✓ | 5.45 ✓ | 5.20 ✓ | 5.17 ✓ |
| `accent.onHero` | 2.85 ✗ | 3.19 ✗ | 3.05 ✗ | 2.90 ✗ | 5.91 ✓ | 2.79 ✗ | 2.96 ✗ | 2.82 ✗ | 2.81 ✗ |
| `brand.red` | 4.47 ✗ | 5.00 ✓ | 4.79 ✓ | 4.55 ✓ | 3.77 ✗ | 4.39 ✗ | 4.65 ✓ | 4.43 ✗ | 4.41 ✗ |

**Dark**

| Text token | page | card | muted | tag | hero | errFill | warnFill | infoFill | okFill |
|---|---|---|---|---|---|---|---|---|---|
| `text.primary` | 17.22 ✓ | 15.14 ✓ | 16.11 ✓ | 13.98 ✓ | 1.00 ✗ | 14.42 ✓ | 12.68 ✓ | 13.73 ✓ | 12.80 ✓ |
| `text.secondary` | 8.15 ✓ | 7.16 ✓ | 7.62 ✓ | 6.61 ✓ | 2.11 ✗ | 6.82 ✓ | 6.00 ✓ | 6.49 ✓ | 6.05 ✓ |
| `text.tertiary` | 6.06 ✓ | 5.33 ✓ | 5.67 ✓ | 4.92 ✓ | 2.84 ✗ | 5.07 ✓ | 4.46 ✗ | 4.83 ✓ | 4.50 ✓ |
| `text.quaternary` | 2.81 ✗ | 2.47 ✗ | 2.63 ✗ | 2.28 ✗ | 6.11 ✓ | 2.35 ✗ | 2.07 ✗ | 2.24 ✗ | 2.09 ✗ |
| `text.onHero` | 1.03 ✗ | 1.09 ✗ | 1.02 ✗ | 1.18 ✗ | 16.59 ✓ | 1.15 ✗ | 1.30 ✗ | 1.20 ✗ | 1.29 ✗ |
| `text.onHeroMuted` | 2.81 ✗ | 2.47 ✗ | 2.63 ✗ | 2.28 ✗ | 6.11 ✓ | 2.35 ✗ | 2.07 ✗ | 2.24 ✗ | 2.09 ✗ |
| `accent.red` | 6.04 ✓ | 5.31 ✓ | 5.65 ✓ | 4.90 ✓ | 2.85 ✗ | 5.06 ✓ | 4.45 ✗ | 4.81 ✓ | 4.49 ✗ |
| `accent.onHero` | 3.28 ✗ | 2.88 ✗ | 3.07 ✗ | 2.66 ✗ | 5.24 ✓ | 2.74 ✗ | 2.41 ✗ | 2.61 ✗ | 2.43 ✗ |
| `brand.red` | 3.85 ✗ | 3.38 ✗ | 3.60 ✗ | 3.12 ✗ | 4.47 ✗ | 3.22 ✗ | 2.83 ✗ | 3.06 ✗ | 2.86 ✗ |

Control and divider edges against the same surfaces. **✓ clears 3:1
(SC 1.4.11)** — the threshold for a boundary you are meant to see. `border.hairline`
divides rows *inside* a card and is deliberately below it; it is not a control edge.

**Light**

| Border token | page | card | muted | tag | hero | errFill | warnFill | infoFill | okFill |
|---|---|---|---|---|---|---|---|---|---|
| `border.hairline` | 1.09 ✗ | 1.22 ✗ | 1.17 ✗ | 1.11 ✗ | 15.41 ✓ | 1.07 ✗ | 1.13 ✗ | 1.08 ✗ | 1.08 ✗ |
| `border.control` | 3.15 ✓ | 3.53 ✓ | 3.38 ✓ | 3.21 ✓ | 5.34 ✓ | 3.09 ✓ | 3.28 ✓ | 3.12 ✓ | 3.11 ✓ |
| `border.focus` | 14.73 ✓ | 16.48 ✓ | 15.79 ✓ | 14.98 ✓ | 1.14 ✗ | 14.44 ✓ | 15.31 ✓ | 14.59 ✓ | 14.53 ✓ |
| `border.focusOnHero` | 1.00 ✗ | 1.11 ✗ | 1.07 ✗ | 1.01 ✗ | 16.86 ✓ | 1.01 ✗ | 1.04 ✗ | 1.00 ✗ | 1.01 ✗ |
| `border.input` | 1.27 ✗ | 1.42 ✗ | 1.36 ✗ | 1.29 ✗ | 13.24 ✓ | 1.24 ✗ | 1.32 ✗ | 1.26 ✗ | 1.25 ✗ |

**Dark**

| Border token | page | card | muted | tag | hero | errFill | warnFill | infoFill | okFill |
|---|---|---|---|---|---|---|---|---|---|
| `border.hairline` | 1.38 ✗ | 1.21 ✗ | 1.29 ✗ | 1.12 ✗ | 12.43 ✓ | 1.15 ✗ | 1.02 ✗ | 1.10 ✗ | 1.02 ✗ |
| `border.control` | 3.59 ✓ | 3.16 ✓ | 3.36 ✓ | 2.92 ✗ | 4.78 ✓ | 3.01 ✓ | 2.64 ✗ | 2.86 ✗ | 2.67 ✗ |
| `border.focus` | 17.22 ✓ | 15.14 ✓ | 16.11 ✓ | 13.98 ✓ | 1.00 ✗ | 14.42 ✓ | 12.68 ✓ | 13.73 ✓ | 12.80 ✓ |
| `border.focusOnHero` | 1.16 ✗ | 1.02 ✗ | 1.09 ✗ | 1.05 ✗ | 14.72 ✓ | 1.02 ✗ | 1.16 ✗ | 1.07 ✗ | 1.15 ✗ |
| `border.input` | 1.70 ✗ | 1.50 ✗ | 1.59 ✗ | 1.38 ✗ | 10.08 ✓ | 1.42 ✗ | 1.25 ✗ | 1.36 ✗ | 1.26 ✗ |

## Feedback pairs

Each state ships a fill, a border and a text colour, used as a set. **Both themes are
published.** They were not, once — only the light borders were, and all four dark borders
sat between 1.36:1 and 1.89:1 against the card while the table showed nothing wrong.

**Light**

| State | Fill | Border | Text | Text on fill | Border on card | Border on page |
|---|---|---|---|---|---|---|
| error | `#FDECEE` | `#C26A76` | `#B73232` | 5.23 ✓ | 3.73 ✓ | 3.34 ✓ |
| warning | `#FFF6E0` | `#9C7718` | `#8A6300` | 5.04 ✓ | 4.14 ✓ | 3.70 ✓ |
| info | `#EAF2FB` | `#5881B0` | `#1F4F8A` | 7.30 ✓ | 4.05 ✓ | 3.62 ✓ |
| success | `#E6F4EC` | `#53956D` | `#1A7A42` | 4.73 ✓ | 3.56 ✓ | 3.18 ✓ |

**Dark**

| State | Fill | Border | Text | Text on fill | Border on card | Border on page |
|---|---|---|---|---|---|---|
| error | `#3A1518` | `#C26A76` | `#FF6B6B` | 5.81 ✓ | 4.53 ✓ | 5.15 ✓ |
| warning | `#332A0E` | `#9C7718` | `#E8C35A` | 8.37 ✓ | 4.08 ✓ | 4.65 ✓ |
| info | `#13263A` | `#5881B0` | `#7FB3E8` | 6.96 ✓ | 4.18 ✓ | 4.75 ✓ |
| success | `#11301E` | `#53956D` | `#5BC487` | 6.61 ✓ | 4.75 ✓ | 5.40 ✓ |


## Radius

| Token | Value |
|---|---|
| `small` | 4 |
| `input` | 8 |
| `card` | 16 |
| `large` | 16 |
| `sheet` | 24 |
| `pill` | 999 |

## Border width

| Token | Value |
|---|---|
| `hairline` | 1 |
| `emphasis` | 2 |

## Motion

| Token | Value |
|---|---|
| `duration` | {"instant":100,"quick":150,"base":200,"enter":250,"exit":180,"slow":350} |
| `easing` | {"standard":"cubic-bezier(0.2, 0, 0, 1)","enter":"cubic-bezier(0, 0, 0, 1)","exit":"cubic-bezier(0.3, 0, 1, 1)"} |

## Opacity

| Token | Value |
|---|---|
| `pressed` | 0.85 |

## Icon size

| Token | Value |
|---|---|
| `inline` | 16 |
| `status` | 19 |
| `control` | 24 |

## Avatar size

| Token | Value |
|---|---|
| `row` | 32 |
| `compact` | 28 |
| `profile` | 88 |

## Swatch size (belt / rank)

| Token | Value |
|---|---|
| `ranked` | 10 |
| `roster` | 16 |
| `subject` | 28 |

## Control size

| Token | Value |
|---|---|
| `grabberWidth` | 36 |
| `grabberHeight` | 4 |
| `multilineMinHeight` | 88 |

## Control height

| Token | Value |
|---|---|
| `primary` | 48 |
| `control` | 44 |
| `dense` | 36 |
| `chip` | 32 |
| `row` | 64 |

## Layout

| Token | Value |
|---|---|
| `feedColumnMaxWidth` | 600 |
| `feedGutter` | 12 |

**Touch target** — `touchTarget` is 44. Anything smaller needs `hitSlop`
making up the difference (SC 2.5.5).
