Colour
Colour
Three layers, and the indirection between them is the whole design. A component
names a mode role; a mode role points at a semantic step or straight at a
primitive; a primitive is the only place a hex literal exists. Nothing in
web/app may name one — test/conformance/token_conformance_test.rb fails a hex
literal, a colour function, or a primitive utility like bg-white anywhere under
app/.
Swatches on this page carry their colour in an inline style, deliberately: a
class named after a primitive would be compiled into the shipped stylesheet by
the very scanner that rule exists to starve.
Mode roles
The 36 roles the application actually paints
with, in both modes. These are what tokens.css emits as :root, .dark, and
the prefers-color-scheme block — which is why there is no dark: variant in
the codebase: the role changes value, the markup does not change class.
Two flags travel with a value and are worth reading. inferred means no capture recorded this role in this mode and it was derived from the observed inversion. adjusted means the observed value failed contrast and was moved a step on its own scale. Both are the record admitting what it knows.
| Role | Light | Dark | Provenance |
|---|---|---|---|
background |
{gray-100}
#f3f4f6
|
{gray-950}
#030712
|
|
surface |
{white}
#ffffff
|
{gray-900}
#101828
|
|
surface-subtle |
{gray-50}
#f9fafb
|
{gray-800}
#1e2939
|
dark · inferred
— follows observed surface inversion white→gray-900, one step lighter than surface
|
surface-inset |
{gray-100}
#f3f4f6
|
{gray-800}
#1e2939
|
dark · inferred
— inline-code background; follows observed inversion pattern
|
surface-code |
{zinc-900}
#18181b
|
{zinc-900}
#18181b
|
|
text |
{gray-900}
#101828
|
{gray-50}
#f9fafb
|
|
text-muted |
{gray-600}
#4a5565
|
{gray-400}
#99a1af
|
light · adjusted
— observed gray-500 is 4.38:1 on gray-100 (fails AA); bumped one step on the same scale
dark · inferred
— mirrors light muted step against dark surfaces
|
text-on-primary |
{white}
#ffffff
|
{white}
#ffffff
|
dark · inferred
— CTA keeps indigo-600 in dark; white stays legible
|
border |
{gray-200}
#e5e7eb
|
{gray-700}
#364153
|
|
border-strong |
{gray-300}
#d1d5dc
|
{gray-600}
#4a5565
|
dark · inferred
— one step stronger than border, mirroring light
|
link |
{indigo-600}
#4f39f6
|
{indigo-400}
#7c86ff
|
dark · inferred, adjusted
— indigo-600 is 2.9:1 on gray-900 (fails AA); lightened on the same scale
|
link-hover |
{indigo-700}
#432dd7
|
{indigo-300}
#a3b3ff
|
dark · inferred
— one step lighter than dark link, mirroring the light hover direction
|
cta |
{indigo-600}
#4f39f6
|
{indigo-600}
#4f39f6
|
dark · inferred
— brand CTA unchanged in dark; white-on-indigo-600 passes AA
|
cta-hover |
{indigo-500}
#615fff
|
{indigo-500}
#615fff
|
dark · inferred
— same hover step as light
|
cta-disabled |
{indigo-300}
#a3b3ff
|
{indigo-800}
#372aac
|
dark · inferred
— dimmed toward the dark surface instead of toward white
|
nav-active-bg |
{indigo-50}
#eef2ff
|
{indigo-900}
#312c85
|
light · inferred
— active nav treatment not directly observable in static CSS; follows indigo component pattern
dark · inferred
— dark counterpart of indigo-50 tint
|
nav-active-text |
{indigo-700}
#432dd7
|
{indigo-200}
#c6d2ff
|
light · inferred
— pairs with nav-active-bg
dark · inferred
— pairs with dark nav-active-bg
|
method-get |
{green-800}
#016630
|
{green-400}
#05df72
|
light · inferred, adjusted
— chip color inferred from GET badge; green-600 is 3.3:1 on white (fails AA), deepened on the same scale
dark · inferred
— brightened on the same scale for dark surfaces
|
badge-danger-bg |
{red-100}
#ffe2e2
|
{red-900}
#82181a
|
dark · inferred
|
badge-danger-text |
{red-800}
#9f0712
|
{red-100}
#ffe2e2
|
dark · inferred
|
badge-warning-bg |
{orange-100}
#ffedd4
|
{orange-900}
#7e2a0c
|
dark · inferred
|
badge-warning-text |
{orange-800}
#9f2d00
|
{orange-100}
#ffedd4
|
dark · inferred
|
badge-success-bg |
{teal-100}
#cbfbf1
|
{teal-900}
#0b4f4a
|
dark · inferred
|
badge-success-text |
{teal-800}
#005f5a
|
{teal-100}
#cbfbf1
|
dark · inferred
|
badge-info-bg |
{blue-100}
#dbeafe
|
{blue-800}
#193cb8
|
dark · inferred
— blue-900 not present in production primitives; blue-800 is the deepest captured step
|
badge-info-text |
{blue-800}
#193cb8
|
{blue-100}
#dbeafe
|
dark · inferred
|
badge-neutral-bg |
{gray-100}
#f3f4f6
|
{gray-800}
#1e2939
|
dark · inferred
|
badge-neutral-text |
{gray-700}
#364153
|
{gray-200}
#e5e7eb
|
dark · inferred
|
badge-primary-bg |
{indigo-100}
#e0e7ff
|
{indigo-900}
#312c85
|
dark · inferred
|
badge-primary-text |
{indigo-800}
#372aac
|
{indigo-100}
#e0e7ff
|
dark · inferred
|
code-fg |
{prism-fg}
#cccccc
|
{prism-fg}
#cccccc
|
|
code-keyword |
{prism-keyword}
#cc99cd
|
{prism-keyword}
#cc99cd
|
|
code-string |
{indigo-300}
#a3b3ff
|
{indigo-300}
#a3b3ff
|
|
code-property |
{yellow-300}
#ffdf20
|
{yellow-300}
#ffdf20
|
|
code-number |
{teal-300}
#46ecd5
|
{teal-300}
#46ecd5
|
|
code-operator |
{lime-300}
#bbf451
|
{lime-300}
#bbf451
|
Semantic ramps
Each ramp is ten steps pointing at one primitive scale. A component never names a step directly — the mode roles above do — but the ramp is what a new role is chosen from.
primary
secondary
success
danger
warning
info
accent
disabled
Primitives
The 92 raw values everything above resolves to. This
page and design/library/colors.html are the only two places permitted to show
them.
red
orange
yellow
lime
green
teal
cyan
blue
indigo
violet
purple
slate
gray
zinc
black
white
prism-fg
prism-keyword