Clarity V2.0.1By Nivoda
Foundations

Color / OKLCH

The OKLCH-based colour system — violet brand, stone neutral, four status palettes.

Every colour in Clarity by Nivoda is authored in OKLCH and stored with a hex fallback. The source of truth is packages/tokens/src/color/primitive.tokens.json; semantic roles (action.primary, status.success) map onto it in semantic.tokens.json.

Why OKLCH

oklch(L C H) is perceptually uniform — a ten-point shift in lightness looks like the same perceived step whether the hue is violet, green, or stone. HSL doesn't do this; a 50% lightness yellow is visibly brighter than a 50% lightness blue. OKLCH also reaches into the P3 wide-gamut space, so displays that support it render saturated hues (brand violet especially) with more chroma than sRGB can hold.

Math is meaningful: step a scale by fixed L intervals and the perceived contrast ladder is even. That's how the 50–950 ramps below were built.

Hex fallbacks are stored alongside because React Native can't parse OKLCH. Web emits oklch(...); native reads hex. One source file, two outputs.

Palette structure

Two layers:

  1. Primitive — raw hue scales, 11 steps each (50, 100, 200, 300, 400, 500, 600, 700, 800, 900, 950). Named after the hue (violet, stone, green, etc.), not the role.
  2. Semantic — role tokens that reference a primitive step. action.primary points at violet.600; surface.base at stone.950 in dark mode. Consumers should reach for semantic tokens; primitives are for designers building new roles.

Violet — brand

Brand hue. The only accent that implies Nivoda. Reserved for primary actions, focus rings, selection states, and the rare moment that needs to lift off the surface. Never chrome, never decorative.

50
100
200
300
400
500
600
700
800
900
950

violet.600 (#6330f5) is the primary action default. violet.500 (#7655fd) is the hover. violet.700 (#5620e1) is the pressed state. Everything else supports focus rings, tinted surfaces, and the rare decorative moment.

It's violet, not purple. The names are not interchangeable in source — reviews catch purple and send it back.

Stone — neutral

Warm neutral. Every surface, every border, every run of body text grounds on stone. Cool greys have been tried and rejected; they fight the luxury-dark direction.

50
100
200
300
400
500
600
700
800
900
950

stone.950 (#0c0a09) is Nivoda black — soft, warm, never pure #000000. Pure black is jarring on a luxury surface; stone.950 reads as dark and considered. Light-mode body text uses stone.900; dark-mode body text uses stone.50.

Status palettes

Four status hues, shown here as the three load-bearing steps (50, 500, 900). Full ramps are in the source file.

Green — success

50 #f3faf6
500 #59b186
900 #25362f

Confirmations, completed states, positive deltas. Maps to status.success.

Red — error

50 #fef2f2
500 #ef4444
900 #7f1d1d

Errors and destructive actions. Maps to status.error and action.destructive.

Blue — info

50 #eef4ff
500 #326cff
900 #192b8f

Informational messages and in-body links. Maps to status.info.

Amber — warning

50 #fffbeb
500 #f59e0b
900 #78350f

Warnings, holds, attention states. Maps to status.warning.

Hex and OKLCH side by side

Each primitive token carries both:

{
  "$value": {
    "colorSpace": "oklch",
    "components": [0.515, 0.2644, 284],
    "hex": "#6330f5"
  }
}

Web builds emit oklch(0.515 0.2644 284) into the CSS file. React Native reads hex. The two values aren't independent — hex is generated from the OKLCH triple at authoring time and checked into source so there's no runtime conversion cost on native.

On this page