design token

Colors

Color system with five base palettes and semantic tokens that adapt to light and dark themes.

The Pindoba color system uses custom color ramps stored as W3C Design Tokens (DTCG format) with automatic light and dark mode adaptation. Five base palettes feed into five semantic color groups.

Base Ramps

Five base color families, each with 11 shades (50–950). Each family has independent light and dark ramps; semantic tokens pick the right one automatically.

blue · primary
dark
50
100
200
300
400
500
600
700
800
900
950
light
50
100
200
300
400
500
600
700
800
900
950
gray · neutral
dark
50
100
200
300
400
500
600
700
800
900
950
light
50
100
200
300
400
500
600
700
800
900
950
green · success
dark
50
100
200
300
400
500
600
700
800
900
950
light
50
100
200
300
400
500
600
700
800
900
950
orange · warning
dark
50
100
200
300
400
500
600
700
800
900
950
light
50
100
200
300
400
500
600
700
800
900
950
red · danger
dark
50
100
200
300
400
500
600
700
800
900
950
light
50
100
200
300
400
500
600
700
800
900
950

Semantic Groups

Every group exposes the same shape: solid fills, text steps, borders, and a five-step surface micro-ramp. Reach for these by intent rather than raw shade.

palette · neutral
surface
surface.peak
surface.hill
surface.base
surface.valley
surface.ground
text
Aa Bb 01text
Aa Bb 01text.bold
Aa Bb 01text.muted
Aa Bb 01text.contrast
border
border
border.muted
border.bold
palette · primary
surface
surface.peak
surface.hill
surface.base
surface.valley
surface.ground
text
Aa Bb 01text
Aa Bb 01text.bold
Aa Bb 01text.muted
Aa Bb 01text.contrast
border
border
border.muted
border.bold
palette · success
surface
surface.peak
surface.hill
surface.base
surface.valley
surface.ground
text
Aa Bb 01text
Aa Bb 01text.bold
Aa Bb 01text.muted
Aa Bb 01text.contrast
border
border
border.muted
border.bold
palette · warning
surface
surface.peak
surface.hill
surface.base
surface.valley
surface.ground
text
Aa Bb 01text
Aa Bb 01text.bold
Aa Bb 01text.muted
Aa Bb 01text.contrast
border
border
border.muted
border.bold
palette · danger
surface
surface.peak
surface.hill
surface.base
surface.valley
surface.ground
text
Aa Bb 01text
Aa Bb 01text.bold
Aa Bb 01text.muted
Aa Bb 01text.contrast
border
border
border.muted
border.bold

How Colors Are Generated

Color ramps are generated in the OKLCH color space using a contrast-based approach:

  • Shade 500 is the anchor — the user-defined color for each family (lightness, chroma, hue)
  • Lighter shades (50–400) progressively reduce WCAG contrast toward 1:1 against the mode background
  • Darker shades (600–950) progressively increase contrast toward 21:1
  • A binary search finds the exact lightness value for each shade that meets its target contrast ratio

Text tokens are contrast-solved against the worst-case surface — both the neutral and own-family surface.ground — so text always meets its target ratio regardless of which surface it sits on.

Surfaces are a 5-stop micro-ramp between shade 50 and shade 100, named as a terrain ladder that reads as structural depth (never as shadow elevation — shadows are a separate axis that composes with any surface):

StopValueRole
surface.peakpegged to shade 50 (light) / 100 (dark)the topmost color: panels, cards, and — with a shadow — popovers and menus
surface.hill25% between the endpointsintermediate raised tier
surface.basemidpointthe page canvas
surface.valley75% between the endpointsinsets, disabled fills
surface.groundpegged to shade 100 (light) / 50 (dark)the app floor: sidebars, window chrome

The endpoints are pegged — byte-identical to their ramp shades and emitted as shade refs, so overriding a base ramp propagates to both ends of the band. Only the three middle stops accept generator surface offsets; the endpoints are tuned through the shade curve itself.

The accent band (accent.surface.*, interactive fills rest → hover → active → pressed) follows the same peg rule: accent.surface.peak = shade 500 and accent.surface.ground = the adjacent anchor shade (600 light / 400 dark, in the direction away from text.contrast.*), both as shade refs — one ramp interval quartered, mirroring the structural band. When a pegged dark ground sits below the ≥2:1 presence floor against the page, the generator raises an accentPresenceTooLow warning (surfaced in devtools) instead of moving the band — retune the far anchor shade’s curve to fix it.

Migrating from the old names: surface.softsurface.peak, surface.step.1surface.hill, surface.step.2surface.base, surface.step.3surface.valley, surface.deepsurface.ground (same mapping under accent.surface.*).

Per-mode palettes: light and dark modes have independent color family inputs. A lightness reference family (default: blue) shares its solved lightness curve with all other families, keeping them visually consistent. Neutral (gray) always uses its own independent lightness curve.

Contrast Colors

Two fixed contrast values are available for cases where you need guaranteed light or dark:

contrast.darkest; // #181818
contrast.lightest; // #f9fbff

Usage

With Panda CSS

import { css } from "@pindoba/styled-system/css";

// Semantic surface and text
<div className={css({
  backgroundColor: "neutral.surface.peak",
  color: "neutral.text.bold",
})}>
  Card content
</div>

// Solid fill button — interactive states step through the accent band
// (rest = accent.surface.peak = shade 500; the same text color passes on
// every band stop by construction)
<button className={css({
  backgroundColor: "primary.accent.surface.peak",
  color: "primary.text.contrast",
  _hover: { backgroundColor: "primary.accent.surface.hill" },
  _active: { backgroundColor: "primary.accent.surface.base" },
})}>
  Submit
</button>

// Subtle surface button
<button className={css({
  backgroundColor: "primary.surface.peak",
  color: "primary.text",
  _hover: { backgroundColor: "primary.surface.base" },
  _active: { backgroundColor: "primary.surface.ground" },
})}>
  Secondary action
</button>

// Status feedback
<div className={css({
  backgroundColor: "danger.surface.peak",
  color: "danger.text.bold",
  borderWidth: "1px",
  borderColor: "danger.border.muted",
})}>
  Error message
</div>

With colorPalette

The colorPalette utility lets components switch between semantic groups without duplicating styles:

<div className={css({ colorPalette: "success" })}>
  <span className={css({ color: "colorPalette.text.bold" })}>
    Status text
  </span>
</div>

<div className={css({ colorPalette: "danger" })}>
  <span className={css({ color: "colorPalette.text.bold" })}>
    Error text
  </span>
</div>

Best Practices

  • Use semantic tokens (primary.surface.peak, neutral.text.bold) rather than raw base colors (blue.light.500)
  • Use .text.contrast for text on solid-colored backgrounds (primary, primary.accent.surface.*) — it passes on every accent-band stop by construction
  • For interactive solid fills, step through the accent band: rest accent.surface.peak, hover accent.surface.hill, active accent.surface.base (there are no dedicated hover/active tokens)
  • Use .text.muted for helper text and secondary content
  • Use .border.muted for subtle separators and .border.bold for emphasis
  • Use surface.peak for light backgrounds and surface.ground for deeper emphasis — they automatically adapt to the active theme mode
  • Borders use alpha-blended variants of the text color: .border (30%), .border.muted (15%), .border.bold (65%)