Skip to main content
Nexus Design System
GitHub

Color

Components use semantic color tokens, like primary-background. Each token picks a shade from a palette, and every text color is checked for contrast before it ships.

The palettes

nxds has five neutral palettes, with eleven shades each. A shade number means the same lightness in every neutral palette, so every 500 looks equally light.

Brand and status colors come from the chromatic palettes (the palettes with a hue, like blue or red). Each hue has its own lightness curve, so yellow stays light and red runs deep. The color-vision filter shows every palette as someone with color blindness sees it.

50
100
200
300
400
500
600
700
800
900
950
slate
neutral
gray
stone
zinc

Brand & status hues

Brand modes are blue, purple, pink, teal, orange, and black. The chromatic brand modes share the OKLCH pipeline; black is a monochrome semantic recipe built from black.base, white.base, and neutral support. Switch to a color-vision filter and watch the red/green pair (error/success) converge: that’s why status never relies on hue alone.

50
100
200
300
400
500
600
700
800
900
950
bluebrand · information
purplebrand
pinkbrand
tealbrand
orangebrand
greensuccess
yellowwarning
rederror
All color scales (17)

The full chromatic set. Brand uses blue, purple, pink, teal, and orange; status uses red, yellow, green, and blue. The rest are raw primitives available for data viz and one-off use.

50
100
200
300
400
500
600
700
800
900
950
red
orange
amber
yellow
lime
green
emerald
teal
cyan
sky
blue
indigo
violet
purple
fuchsia
pink
rose

Live tokens

These are the tokens components use. Change the theme with the theme picker, and every pair updates.

A foreground token names the surface it sits on, not what it paints. primary-foreground colors anything on primary-background: a button’s label, or a checked switch’s knob. error-subtle-foreground colors the message and icon on error-subtle.

Each foreground is checked against the surfaces nxds pairs it with. On any other surface, check the contrast yourself.

Aabackground
Aamuted
Aacontainer
Aapopover
Aaprimary

What each shade is for

The shade number is a lightness step. Each token uses a different step in each mode, so it keeps the same visual weight on a dark background.

Text and thin borders don’t use a shade. foreground, muted-foreground and border.default are semi-transparent black in light mode and white in dark mode, so they work on any surface.

ShadeRoleLight-mode useDark-mode use
50Near-whitemuted, disabled, background/container/popover-hovernav-foreground
100Very lightbackground-active, container-active, control-background, nav-background—
200Lightcontrol-background-hover, nav-item-hover/active, nav-border—
300Light-medium—disabled-foreground, nav-muted-foreground
400Medium-lightborder-focus, disabled-foregroundborder-focus
500Mid— (perceptual anchor)— (anchor)
600Medium-darknav-muted-foreground, brand -background—
700Darkbrand -background-hovercontrol-background-hover, popover-hover
800Very darkbrand -background-activecontrol-background, popover, container-hover, nav-border
900Darkercontainer-foreground, popover-foreground, nav-foregroundcontainer, muted, background-hover/active, nav-item-hover/active
950Near-black— (rarely surfaced)background — the canvas, nav-background, disabled

Accessibility

nxds checks contrast with APCA (a perceptual contrast score), not WCAG 2 ratios. WCAG 2 ratios misjudge the mid-tone pairs a token system produces all the time.

Each pair has a minimum score for how it’s used. CI derives themes across many brand colors, surface tones and contrast settings, then checks every pair in each one. If any pair falls below its minimum, the build fails.

CI also checks that the palettes stay distinct for color-blind users.

Every foreground is checked against each surface it can sit on, including hover, pressed and translucent states. If a brand or status label can’t reach its minimum, nxds shifts that fill’s shade. Otherwise the fill keeps the brand color you chose.

PairMin APCACovers
foreground ↔ backgroundLc ≥ 75Body text
{brand,status}-foreground ↔ -backgroundLc ≥ 60UI labels — buttons, badges
*-subtle-foreground ↔ -subtleLc ≥ 60Labels on tinted fills
nav-foreground ↔ nav surfacesLc ≥ 60Nav label text
chart.categorical ↔ containerLc ≥ 60Chart marks
muted-foreground ↔ mutedLc ≥ 45Incidental / de-emphasised text
disabled-foreground ↔ disabledLc ≥ 45Disabled-state text
focus ring ↔ every surfaceLc ≥ 45Focus indicators

Contrast setting

The lightContrast and darkContrast fields in Appearance raise the minimums as they go from 0 to 100. When a target can’t be reached, nxds uses the highest contrast possible. The minimums at 0 always apply.

TargetAt 0At 100
Body textLc 75Lc 90
UI labelsLc 60Lc 75
Incidental textLc 45Lc 60
Light-mode border opacity6%11.68%

How it works

  1. 1
    Store hex

    Tokens live as hex on disk.

  2. 2
    Convert to OKLCH

    At build time, every hex value becomes an oklch(…) value in the CSS.

  3. 3
    Pin the lightness

    Each shade’s lightness is set from a perceptual grid. The neutral palettes share one grid. Each chromatic hue follows its own curve and takes the most vivid chroma Display-P3 allows.

  4. 4
    Gate with APCA

    Every foreground and background pair is scored with APCA. A failing pair blocks the build.