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.

Facet Visual 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. Convert seed RGB → HCT (HueChromaTone) color space
  2. Extract Hue from seed
  3. Generate 13 tones (0, 10, 20, …, 95, 99, 100) at consistent

    chroma per role

  4. Bind 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 — Role-to-tone 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