Konjo tokens — the complete tables
Every number and every ratio below is read straight out of
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
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).