pageweave/resources

Color Systems

Build perceptually uniform color systems in OKLCH — from primitives through semantic roles to dark-mode-safe themes that pass WCAG contrast.

· .md · source ↗

Build color systems that are perceptually uniform, accessible, and themeable. Work in OKLCH — it matches how humans see lightness and color, unlike hex or HSL.

Why OKLCH

The Primitive Palette

Build a scale from 50 to 950 for each hue. Keep chroma consistent within a hue family.

/* Neutral scale (chroma 0.01–0.02, hue toward brand) */
--gray-50:  oklch(0.98 0.005 240);
--gray-100: oklch(0.96 0.008 240);
--gray-200: oklch(0.92 0.01 240);
--gray-300: oklch(0.87 0.012 240);
--gray-400: oklch(0.71 0.015 240);
--gray-500: oklch(0.55 0.02 240);
--gray-600: oklch(0.45 0.02 240);
--gray-700: oklch(0.37 0.02 240);
--gray-800: oklch(0.27 0.015 240);
--gray-900: oklch(0.2 0.01 240);
--gray-950: oklch(0.14 0.008 240);

/* Brand scale (chroma 0.12–0.2, consistent hue) */
--blue-50:  oklch(0.97 0.02 240);
--blue-100: oklch(0.93 0.04 240);
--blue-200: oklch(0.88 0.08 240);
--blue-300: oklch(0.8 0.12 240);
--blue-400: oklch(0.7 0.16 240);
--blue-500: oklch(0.55 0.2 240);   /* primary */
--blue-600: oklch(0.48 0.2 240);
--blue-700: oklch(0.4 0.18 240);
--blue-800: oklch(0.32 0.14 240);
--blue-900: oklch(0.25 0.1 240);

Rules:

The Semantic Layer

Map primitives to roles. This layer changes for dark mode; primitives stay.

/* Foreground hierarchy */
--fg-primary: var(--gray-900);      /* headings, body text */
--fg-secondary: var(--gray-600);    /* descriptions, labels */
--fg-tertiary: var(--gray-400);     /* hints, placeholders */
--fg-muted: var(--gray-300);        /* disabled, decorative */

/* Background elevation */
--bg-base: var(--gray-50);          /* page background */
--bg-raised: var(--white);          /* cards, modals */
--bg-sunken: var(--gray-100);       /* inputs, code blocks */
--bg-overlay: oklch(0 0 0 / 0.5);  /* modal backdrop */

/* Border progression */
--border-subtle: var(--gray-200);   /* card borders, dividers */
--border-default: var(--gray-300);  /* input borders */
--border-strong: var(--gray-400);   /* focus rings */

/* Brand */
--color-primary: var(--blue-500);
--color-primary-hover: var(--blue-600);
--color-primary-active: var(--blue-700);
--color-primary-content: var(--white);

/* Semantic feedback */
--color-success: oklch(0.6 0.17 155);
--color-warning: oklch(0.75 0.16 85);
--color-error: oklch(0.6 0.2 25);
--color-info: oklch(0.6 0.15 240);

Dark Mode

Swap semantics, keep primitives:

[data-theme="dark"] {
  --fg-primary: var(--gray-100);
  --fg-secondary: var(--gray-400);
  --fg-tertiary: var(--gray-500);
  --bg-base: var(--gray-950);
  --bg-raised: var(--gray-900);
  --bg-sunken: var(--gray-800);
  --border-subtle: var(--gray-800);
  --border-default: var(--gray-700);
  --color-primary: var(--blue-400);
  --color-primary-hover: var(--blue-300);
  --color-primary-content: var(--gray-950);
}

Rules:

Contrast Verification

The fast OKLCH contrast check: lightness difference ≥ 0.4 almost always passes WCAG AA (4.5:1).

For precise checks:

Every *-content token must pass contrast against its parent:

Color Usage Rules

  1. One accent color, used with intention. One primary color carries action. Secondary and accent are for rare emphasis.
  2. Semantic colors are functional. Success/warning/error appear in feedback contexts only — never decoration.
  3. Opacity modifiers over new colors. text-base-content/60 gives you a dimmed version that survives theming. Don't create --text-dim when opacity works.
  4. Never use color alone to convey information. Pair with text, icon, or pattern.
  5. Test the full chain. primary → primary-hover → primary-active → primary-content. Each step should be visibly distinct.

Palette Anti-Patterns

PageWeave Workflow

  1. Define primitives and semantics in OKLCH.
  2. Pass bare variables to update_theme(website, config: { css }) — no [data-theme] wrapper.
  3. Dark themes: include color-scheme: dark;.
  4. Test: screenshot a page with buttons, badges, inputs, tables, and an alert — all five exercise semantic colors.
  5. Verify contrast for every text/background pair.
  6. Check .md view: code blocks inherit theme colors.