Dynamic color

Generate a complete Koder color scheme from a single seed color — user-supplied (brand) or extracted from a wallpaper / hero image. Material parity (`/styles/color/dynamic/user-generated-source`, aka "Material You"). Output: 12 canonical color-schemes presets' worth of tokens derived algorithmically from one input.

Spec — Dynamic color

Facet *isual*do Koder Design. Material parity: https://m3.material.io/styles/color/dynamic/user-generated-source.

Marked mandatory: false — dynamic color is an OPTIONAL surface feature; apps may ship with only the canonical presets and expose no seed-color picker.

What it does

Input: 1 seed color (hex, e.g. #3B5BFD). Output: a full token bundle compatible with color-roles.kmdbg, surface, surface-variant, text, text-muted, accent, accent-strong, accent-on, error, error-bg, etc. — for both light and dark variants.

The token bundle plugs into the same renderer as the 12 canonical presets (color-schemes.kmd Catalog), so widgets see no difference between a dynamic scheme and a canonical one.

R1 — Tonal palette generation

From the seed:

  1. *onvert*seed RGB → HCT (HueChromaTone) color space
  2. *xtract*Hue from seed
  3. *enerate*13 tones (0, 10, 20, …, 95, 99, 100) at consistent

    chroma per role

  4. *ind*roles to tones per the role-tone map (see R2)

Algorithm: based on Material Color Utilities (@material/material-color-utilities) or equivalent HCT library. Koder ships a Dart port in koder_kit + JS port in koder_web_kit (planned).

R2 — Roletotone bindings

Role Light tone Dark tone
bg 99 10
surface 95 20
surface-variant 90 30
text 10 90
text-muted 30 70
text-subtle 50 60
accent 40 80
accent-strong 30 90
accent-on 99 20
accent-tint accent @ 12% accent @ 12%
error (fixed hue) (fixed hue)
success (fixed hue) (fixed hue)
warning (fixed hue) (fixed hue)
info (fixed hue) (fixed hue)
border 80 40
focus accent + saturated accent + saturated

Status colors (errorsuccesswarning/info) keep fixed hues regardless of seed — preserves semantic meaning across themes (red error reads as error even with green-seeded brand).

R3 — Extraction from image

When the seed is an image (wallpaper, hero):

  1. Resize to 128×128 to bound compute
  2. K-means quantize to 32 colors
  3. Score each color: chroma × population × saturation
  4. Pick top-scoring color as seed
  5. Run R1 algorithm with that seed

Honors prefers-reduced-motion (no extraction animation) and runs off main thread (Worker on web; isolate in Flutter).

R4 — Quality gates

Dynamic color must pass the same AAA contrast gates as canonical presets (color-schemes.kmd). If the seed produces a palette that fails any required pair:

  • Auto-adjust: shift the failing role's tone toward higher contrast
  • Or: reject the seed and prompt the user with the offending failure
  • Never silently produce inaccessible output

R5 — Persistence

Dynamic scheme persists per-user (synced via Koder ID) when the seed source is the user's deliberate choice. Per-surface (local) when the seed comes from device wallpaper (which itself is local).

Storage key: koder.color_scheme.dynamic = {seed: "#...", source: "manual"|"wallpaper", generated_at: ISO8601}. The generated tokens are reproducible from seed alone, so we don't store the full token bundle.

R6 — Switching between dynamic and canonical

The color scheme picker in Settings shows:

  • 12 canonical presets (cards with name + preview swatch)
  • "Custom" option that opens a seed-color picker
  • "Match device wallpaper" toggle (when supported by the platform)

Switching is instant; the new tokens replace via CSS custom property update (no full reload).

R7 — Supported platforms

Platform Wallpaper extraction Manual seed Notes
Android 12+ ✅ (Material You system API) Native dynamic color hooks
iOS ❌ wallpaper (sandbox) Manual seed only
macOS ⚠️ accent color from OS Limited
Linux desktop ❌ (no canonical API) Manual seed only
Windows 11 ⚠️ accent color from OS Limited
Web ❌ wallpaper Manual seed only
  • color-schemes.kmd — canonical presets dynamic schemes drop into
  • color-roles.kmd — role taxonomy dynamic schemes must satisfy
  • customization.kmd — Settings binding location

Source: ../home/koder/dev/koder/meta/docs/stack/specs/themes/color-dynamic.kmd