Skip to content

ADR-19: OKLCH Design Token System with Cloud Dancer Theme

🇰🇷 한국어 버전

DateAuthorRepos
2024-12-20@KubrickCodeweb

Context

Default Theme Limitations

The shadcn/ui default theme uses neutral grayscale colors with zero chroma, resulting in a cold, clinical appearance that lacks visual warmth and personality.

Dark/Light Mode Inconsistency

Hardcoded Tailwind color classes (e.g., text-green-600, bg-yellow-500) created inconsistent appearances between light and dark modes. Colors that worked well in light mode appeared too bright or washed out in dark mode.

Color Space Limitations

Traditional color spaces (RGB, HSL) have fundamental limitations:

  • RGB/Hex: Difficult to manipulate programmatically for palette generation
  • HSL: Perceptually non-uniform - equal lightness values appear differently across hues

Test Status Visualization

The application displays test results with multiple status states (active, focused, skipped, todo, xfail), requiring a systematic approach to status-specific colors that work consistently across themes.

Decision

Adopt OKLCH color space with CSS variables as the design token system for consistent, accessible theming.

Core principles:

  1. OKLCH Color Space: Use perceptually uniform color representation
  2. Warm Palette: Apply Pantone 2026 Cloud Dancer-inspired warm undertones
  3. Semantic Variables: Define purpose-based color tokens instead of spectrum-based
  4. Tailwind Integration: Expose tokens via @theme directive for utility class generation

Token Categories

CategoryVariablesPurpose
Corebackground, foreground, card, popover, primary, secondary, muted, accentBase UI surfaces and text
Statedestructive, border, input, ringInteractive element states
Chartchart-1 through chart-5Data visualization
Statusstatus-active, status-focused, status-skipped, status-todo, status-xfailTest result indicators
Custominput-bg, hero-gradient-center, hero-gradient-edgeComponent-specific overrides

Color System Parameters

ParameterLight ModeDark ModePurpose
Lightness0.885 - 0.9750.185 - 0.320Base brightness level
Chroma0.004 - 0.0100.010 - 0.014Subtle warmth saturation
Hue95° - 98°95° - 98°Warm beige/sand direction

Options Considered

Option A: OKLCH Color Space (Selected)

OKLCH uses Lightness, Chroma, and Hue with perceptual uniformity based on the Oklab color model.

Pros:

  • Perceptual Uniformity: Equal numerical steps produce equal visual changes
  • Consistent Lightness: Same L value appears equally bright across all hues
  • Wide Gamut Support: Native Display P3 and beyond color representation
  • Predictable Accessibility: Reliable WCAG contrast ratio calculations
  • Better Gradients: No muddy intermediate colors in color interpolation
  • Browser Support: 92%+ global support as of 2025

Cons:

  • Learning curve for developers unfamiliar with OKLCH
  • Out-of-gamut values require clipping awareness
  • Legacy browser fallback needed (<8% usage)

Option B: HSL Variables

How It Works:

CSS variables with HSL values, leveraging its intuitive Hue-Saturation-Lightness model.

Evaluation:

  • More intuitive hue selection (0-360° color wheel)
  • Rejected: Perceptually non-uniform - yellow appears brighter than blue at same L value
  • Inconsistent palette generation results

Option C: RGB/Hex with Tailwind Palette

How It Works:

Use standard Tailwind color palette (slate, gray, zinc) with hardcoded hex values.

Evaluation:

  • Zero configuration, works out of box
  • Rejected: No theme customization, cold neutral appearance
  • Difficult to maintain dark/light mode parity

Implementation

@theme Directive Integration

Tokens are exposed to Tailwind via the @theme inline directive:

css
@theme inline {
  --color-background: var(--background);
  --color-status-active: var(--status-active);
  /* ... */
}

This enables utility class generation (bg-background, text-status-active) while maintaining single source of truth in CSS variables.

Theme Mode Strategy

Light and dark modes share identical hue angles (95°-98°) but invert lightness and adjust chroma:

  • Light: High lightness (0.9+), low chroma (0.004-0.010)
  • Dark: Low lightness (0.2-0.3), slightly higher chroma (0.010-0.014) for visibility

Status Color Mapping

StatusLight Mode HueDark Mode L AdjustmentSemantic Meaning
active145° (green)+0.10Passing/successful
focused310° (magenta)+0.10Currently selected
skipped85° (yellow)+0.07Intentionally bypassed
todo240° (blue)+0.10Pending implementation
xfail25° (orange)+0.08Expected failure

Consequences

Positive

Visual Consistency:

  • Unified warm appearance across all components
  • Seamless light/dark mode transitions
  • Professional, approachable aesthetic

Developer Experience:

  • Semantic naming reduces cognitive load (text-status-active vs text-green-600)
  • Single source of truth for color modifications
  • Tailwind utility classes auto-generated from tokens

Accessibility:

  • Predictable contrast ratios due to perceptual uniformity
  • Status colors maintain distinction in both modes
  • Consistent reading experience across themes

Future-Proofing:

  • Wide gamut ready for HDR displays
  • Tailwind v4 native OKLCH alignment

Negative

Learning Curve:

  • OKLCH less familiar than HSL/RGB
  • Mitigation: Document common values and provide conversion tools

Browser Compatibility:

  • ~8% browsers lack OKLCH support
  • Mitigation: Acceptable for target audience (developers using modern browsers)

Gamut Clipping:

  • Some OKLCH values exceed sRGB display capabilities
  • Mitigation: All production values tested within sRGB gamut

References

Open-source test coverage insights