Skip to content

Dynamic color

themes specs/themes/color-dynamic.kmd

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.

When this spec applies

Primary triggers

All triggers

Specification body

Spec — Dynamic color

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 (Hue/Chroma/Tone) 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

RoleLight toneDark tone
bg9910
surface9520
surface-variant9030
text1090
text-muted3070
text-subtle5060
accent4080
accent-strong3090
accent-on9920
accent-tintaccent @ 12%accent @ 12%
error(fixed hue)(fixed hue)
success(fixed hue)(fixed hue)
warning(fixed hue)(fixed hue)
info(fixed hue)(fixed hue)
border8040
focusaccent + saturatedaccent + saturated

Status colors (error/success/warning/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

PlatformWallpaper extractionManual seedNotes
Android 12+✅ (Material You system API)Native dynamic color hooks
iOS❌ wallpaper (sandbox)Manual seed only
macOS⚠️ accent color from OSLimited
Linux desktop❌ (no canonical API)Manual seed only
Windows 11⚠️ accent color from OSLimited
Web❌ wallpaperManual 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

References