Design System
Foundations · Theme

Dark mode.

A complete mirror of the light-mode token set, tuned for dark surfaces. The brand stays iris, but the anchor shifts from iris-500 to iris-300 so accents stay readable. Neutrals drop into a violet-tinted near-black instead of pure black — warmer, and consistent with the brand.

Theme · dark

Neutrals

Background, surface, ink, and rule — mirrored. Dark mode uses a violet-black (#0B0A14) rather than pure black; it reads as deliberate and keeps brand warmth.

Lightdefault
--bg#FBFAFE
--bg-elev#FFFFFF
--bg-elev-2#F1F5F9
--ink#0F172A
--ink-muted#475569
--ink-subtle#64748B
--rule#E2E8F0
Dark[data-theme=dark]
--bg#0B0A14
--bg-elev#151427
--bg-elev-2#1E1C36
--ink#F5F3FF
--ink-muted#A8A3C7
--ink-subtle#6E6A8E
--rule#2A2744

Brand accents shift up

On dark, step 500 gets muddy. The anchor moves to step 300 so accents keep their punch, and paired muted backgrounds drop to step 800.

Light · anchor 500
iris-500 coral-500 teal-500
iris-100 coral-100 teal-100
500 on white · 100 as muted surface
Dark · anchor 300
iris-300 coral-300 teal-300
iris-800 coral-800 teal-800
300 on dark · 800 as muted surface

Components in situ

Identical content, both themes. Every color comes from a role alias; the component code doesn't change.

Create a workspace

Give it a name and invite teammates. You can change both later.

Free New Preview
Create a workspace

Give it a name and invite teammates. You can change both later.

Free New Preview

Elevation without shadow

On dark, shadows disappear into the background. Elevation is communicated by lifting the surface lightness one step at a time — and borders get a touch stronger.

Level 0--bg#0B0A14
Level 1--bg-elev#151427
Level 2--bg-elev-2#1E1C36
Level 3popover / modal#2A2744 + shadow

Rules

Dark mode is a translation, not a reskin. Same layout, same hierarchy, same semantics.

Do

Use role aliases

Components reference --color-primary, not --iris-500. The alias resolves differently per theme — the component doesn't need to know.

Don't

Don't invert hex values

A flipped palette always looks off. The dark set is tuned per-token — lightness, chroma, and contrast are all re-balanced, not reflected.

Do

Raise, don't shadow

Shadows vanish against black. Use a one-step lighter surface for elevation; reserve real shadows for popovers and modals floating over content.

Don't

Don't use pure black or white

#000 and #FFF glare. Ink is #F5F3FF (subtle iris warmth); background is #0B0A14 — violet-black, never flat black.

Do

Test contrast both ways

Every alias has to clear WCAG AA in both themes. Coral-500 on white passes; coral-500 on dark fails — so dark uses coral-300.

Don't

Don't forget charts

Viz palettes need their own dark tuning — line strokes thicken by .5px, gridlines use --d-rule, fills drop opacity to 0.6 so underlying grid stays visible.

Tokens

Wrap in [data-theme="dark"] on <html> or <body>. Tokens cascade everywhere.

[data-theme="dark"] {
  /* Neutrals */
  --bg:          #0B0A14;
  --bg-elev:     #151427;
  --bg-elev-2:   #1E1C36;
  --ink:         #F5F3FF;
  --ink-muted:   #A8A3C7;
  --ink-subtle:  #6E6A8E;
  --rule:        #2A2744;
  --rule-strong: #3A3658;

  /* Role aliases — anchor shifts to step 300 */
  --color-primary:       var(--iris-300);
  --color-primary-fg:    var(--iris-950);
  --color-primary-hover: var(--iris-200);
  --color-primary-muted: var(--iris-800);
  --color-primary-subtle:#1A0D52;

  --color-secondary:       var(--coral-300);
  --color-secondary-fg:    var(--coral-950);
  --color-secondary-hover: var(--coral-200);
  --color-secondary-muted: var(--coral-800);

  --color-accent:       var(--teal-300);
  --color-accent-fg:    var(--teal-950);
  --color-accent-hover: var(--teal-200);
  --color-accent-muted: var(--teal-800);

  /* State — lifted 200 steps for dark contrast */
  --color-success: #34D399;
  --color-warning: #FBBF24;
  --color-danger:  #F87171;
  --color-info:    #60A5FA;

  /* Elevation uses surface, not shadow */
  --shadow-sm: none;
  --shadow-md: 0 4px 12px -4px rgba(0,0,0,.5);
  --shadow-lg: 0 20px 40px -20px rgba(0,0,0,.7);
}

/* Optional — follow OS preference */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    /* repeat block above */
  }
}