Andromeda Pro · Foundation
The primitives
Every Andromeda Pro component is built from the values on this page, read through three layers of tokens. This page is the what. The Brain teaches when and how to use each value.
Three layers
- 100200300400500
Layer 1
Primitives
The neutral ladder and four hue families. A theme retunes these; a component never reads them.
- status.info.textbrand 300
Layer 2
Semantic
Named roles such as
status.danger.text. Every component reads this layer and only this one. - Live
Layer 3
Component wires
A
--component-rolevariable in the one file that owns it, pointing at exactly one role. The wiring never leaves that file.
The neutral ladder
Thirteen grays, numbered by depth: 100 is the page ground, 1300 the strongest ink. Surfaces, borders and text are all roles pointing into this one ladder.
light
dark
Four hue families
Five stops each, 100 through 500, plus two alphas and a guaranteed-contrast on ink. The ramp itself is the same in both themes; what flips is which stop a role points at, shown further down. Color is never the only channel: a tone always rides with a glyph, a position or a label.
Brand
the live series, the primary action
light
dark
Success
this moved the good way
light
dark
Warning
caution, degraded, restricted
light
dark
Danger
loss, fault, threshold breach
light
dark
Semantic roles
Components never read a primitive. They say what they mean, in eight role groups:
textthe four inks: primary, secondary, muted, faintsurfacethe grounds and the interaction steps a fill climbsborderthe hairline ladder, subtle to strong, plus the floating framestatusfour tones × six roles: surface, border, text, on, onMuted, fillactionwhat a control does: primary, secondary, danger, and their statesfocusthe ring, the glow, and their invalid formsselectionthe mark, the track, the selected edgechartbaseline, live, context, threshold, grid, and the stacked rampHow a family crosses the themes
When the theme flips, a role’s stop mirrors around the middle of its family: 100 and 500 swap, 200 and 400 swap, and 300 is the pivot. Fills stop at 200 rather than 100, because 100 barely stands off the light page. The one thing that overrules the mirror is WCAG. Where the mirrored value misses its contrast minimum on the light page ground it steps toward the deep end until it clears. That is the whole reason the text row below reads 300 → 500 rather than 300 → 400: on the light ground the 400 stop measures 3.97 for brand, 4.21 for success and 3.12 for warning, all under the 4.5 that normal text requires, so they step again. Danger reaches 4.86 at 400 and stops there. The row under it lands on 400 everywhere because a mark is a non-text object and needs 3.0, not 4.5. Both columns are resolved from the same functions the site paints with, so they cannot drift from what you see.
Text
a status word, a value, a chart label
Secondary text
the quieter second line beside it
Foreground and borders
icons, dots, chart lines, frames
Background fill
a rail, a filled chip, a track
Every color, both themes
The whole set, dark on the left of each pair and light on the right. Enumerated from the live token set rather than listed by hand, so a color added to the system shows up here on its own. A pair marked same is deliberately theme invariant: a scrim darkens what is behind it whatever the theme, and a solid fill that carries pale text has to stay deep in both.
Neutrals
numbered by depth in the stack, not by lightness, so 100 is the page ground in both themes and the ladder never folds
neutral-100neutral-200neutral-300neutral-400neutral-500neutral-600neutral-700neutral-800neutral-900neutral-1000neutral-1100neutral-1200neutral-1300neutral-scrimsameText
the four neutral inks
text-primarytext-secondarytext-mutedtext-faintSurfaces
grounds and the steps a fill climbs under the pointer
surface-floatingsurface-scrimsamesurface-basesurface-raisedsurface-overlaysurface-hoversurface-activesurface-alphaBorders
the hairline ladder
border-floatingborder-subtleborder-baseborder-brightborder-strongBrand ramp
the primitive stops every brand role points at
brand-100samebrand-200samebrand-300samebrand-400samebrand-500samebrand-alphasamebrand-alpha-strongsamebrand-onsamebrand-on-brightsameStatus
four tones, six roles each
status-success-surfacesamestatus-success-borderstatus-success-onstatus-success-on-mutedstatus-success-textstatus-success-fillstatus-success-markstatus-success-areasamestatus-warning-surfacesamestatus-warning-borderstatus-warning-onstatus-warning-on-mutedstatus-warning-textstatus-warning-fillstatus-warning-markstatus-warning-areasamestatus-danger-surfacesamestatus-danger-borderstatus-danger-onstatus-danger-on-mutedstatus-danger-textstatus-danger-fillstatus-danger-markstatus-danger-areasamestatus-info-surfacesamestatus-info-borderstatus-info-onstatus-info-on-mutedstatus-info-textstatus-info-fillstatus-info-markstatus-info-areasameActions
what a control does
action-primarysameaction-primary-hoversameaction-primary-pressedsameaction-primary-bordersameaction-primary-border-hoversameaction-on-primarysameaction-secondaryaction-secondary-hoveraction-secondary-pressedaction-secondary-borderaction-secondary-border-hoveraction-on-secondaryaction-ghost-hoveraction-ghost-pressedaction-on-ghostaction-on-ghost-hoveraction-dangersameaction-danger-hoversameaction-danger-bordersameaction-danger-border-hoversameaction-on-dangersameaction-danger-text-hoveraction-active-textaction-active-glowFocus
the ring and its glow
focus-ringfocus-ring-invalidsameSelection
checked, selected, current
selection-borderselection-border-hoverselection-surfacesameselection-markselection-tracksameChart ink
roles, not series numbers
chart-baselinechart-livechart-contextchart-thresholdsamechart-comparechart-gridchart-cursorchart-axischart-stack1chart-stack2samechart-stack3chart-stack4Gradients
composed values, swapped stop by stop
gradient-brand-fadegradient-brand-sweepgradient-surface-softAccessibility
Every color pair in this system meets WCAG 2.2 level AA, in both themes. The ratios are measured by a contrast script, not judged by eye, and contrast is the one thing here that overrules a design decision, including the family pivot above.
4.5 : 1
Normal text
Anything under 24px, or under 18.66px bold. Almost all of our text. WCAG 1.4.3.
3.0 : 1
Large text and non-text
Display sizes, and every icon, border, chart line, dot and focus ring. WCAG 1.4.3 and 1.4.11.
7.0 : 1
AAA, where it lands
Not a target for the whole system, but the primary, secondary and muted text inks clear it in both themes.
The type ramp
A step names three values, not one: a size, its leading, and its tracking. Taking a step means taking all three. Tracking is a function of size here, open at the small end where letterforms crowd and pulled in at the display end where large type sets loose. Weight is the second axis and stays a component’s choice.
textXsAa10px / 0.625rem14px / 0.875rem0.14em / 14%textSmAa12px / 0.75rem18px / 1.125rem0.08em / 8%textMdAa14px / 0.875rem20px / 1.25rem0.02em / 2%textLgAa16px / 1rem24px / 1.5rem0.02em / 2%textXlAa18px / 1.125rem28px / 1.75rem0text2xlAa20px / 1.25rem30px / 1.875rem0displayXsAa24px / 1.5rem32px / 2rem-0.02em / -2%displaySmAa30px / 1.875rem38px / 2.375rem-0.02em / -2%displayMdAa36px / 2.25rem44px / 2.75rem-0.02em / -2%displayLgAa48px / 3rem60px / 3.75rem-0.02em / -2%displayXlAa60px / 3.75rem72px / 4.5rem-0.02em / -2%display2xlAa72px / 4.5rem90px / 5.625rem-0.02em / -2%The one named style
Everything in the system is a step plus a weight. There is exactly one exception, because it carries something the ramp deliberately does not: case. Uppercase is a decision a component makes, not a property of a size, so it lives here and nowhere else. It carries no color either, so ink stays a separate decision.
typography.role.label10 / 14 · medium · wider · uppercaseBearing
column heads, axis and legend labels, kickers, stat captions
Spacing
A 4px grid with one half-step. Control heights ride their own three-rung ladder (28 / 32 / 40) so fields, buttons and toggles in a row always agree.
14px1.56px28px312px416px520px624px832px1040px1248px