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.kmd — bg, 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:
- *onvert*seed RGB → HCT (HueChromaTone) color space
- *xtract*Hue from seed
- *enerate*13 tones (0, 10, 20, …, 95, 99, 100) at consistent
chroma per role
- *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):
- Resize to 128×128 to bound compute
- K-means quantize to 32 colors
- Score each color: chroma × population × saturation
- Pick top-scoring color as seed
- 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 |
Cross-link
color-schemes.kmd— canonical presets dynamic schemes drop intocolor-roles.kmd— role taxonomy dynamic schemes must satisfycustomization.kmd— Settings binding location