Color — Advanced customization
How tenants + users customize the Koder color system beyond preset picking — per-role overrides, brand color injection, accessibility presets, custom error/warning hues. Material parity (`/styles/color/advanced/overview`). Read after `color-schemes.kmd` + `color-roles.kmd`; this spec covers the escape hatches.
Spec — Color: Advanced customization
Facet *isual*do Koder Design. Material parity: https://m3.material.io/styles/color/advanced/overview.
Levels of customization
| Level | Who | Where stored | Spec |
|---|---|---|---|
| *0* | Preset pick from 12 canonical | per |
color-schemes.kmd |
| *1* | Dynamic from seed | per |
color-dynamic.kmd |
| *2* | Per-role override | per |
this spec |
| *3* | Full custom scheme JSON | per-tenant | this spec |
| *4* | Programmatic theme (SDK) | per-app | koder_kit API |
Higher levels override lower. L2/L3 are the "advanced" escape hatches.
R1 — L2: Per-role override
Allows changing 1-2 roles while keeping the rest of the preset. Common cases:
- Brand accent: override
accent+accent-strong+accent-on - Brand error: override
errorto match brand-house color - Accessibility: bump
focusto a higher-contrast variant
Override storage shape:
{
"base_scheme": "default",
"overrides": {
"accent": "#E91E63",
"accent-strong": "#AD1457",
"accent-on": "#FFFFFF"
}
}When loading: start with the base scheme's tokens, then Object.assign the overrides. AAA contrast gates run on the final result — failures prompt the user to adjust.
R2 — L3: Full custom scheme
Tenant admin uploads a complete token bundle JSON (matches the shape of canonical preset entries from color-schemes.kmd Catalog):
{
"name": "Acme Corp",
"variants": {
"light": {
"bg": "#FFFFFF",
"surface": "#F5F5F7",
...
},
"dark": {
"bg": "#0B1220",
...
}
}
}Validation pipeline:
- Schema check (all required tokens present)
- Hex format check (
#RRGGBB) - AAA contrast check on every required pair (per
color-roles.kmdR4) - Preview render at known canonical pages
- Owner approval gate (workspace admin signs off)
R3 — Brand color injection (tenant-scoped)
The most common L2 use case: a tenant wants their brand color threaded through every Koder app the tenant uses.
Mechanism:
- Workspace admin sets brand color in Koder ID console
- Koder ID propagates to all tenant-scoped apps via
tenant_themeclaim in the user's JWT
- Apps merge
tenant_themeonto the user's chosen preset at load
Precedence (lowest to highest):
- Surface default preset (per-device)
- User
chosen preset (peruser, synced) - User dynamic seed (per-user)
- Tenant brand override (per
tenant, applied via L2 on top of #13) - Per-app explicit override (programmatic)
R4 — Color-blindness presets
Pre-built L2 override packs for common color vision differences:
| Pack | Override |
|---|---|
| Protanopia (red-blind) | Shift error from red to magenta; success keeps |
| Deuteranopia (green-blind) | Shift success from green to teal; error keeps |
| Tritanopia (blue-blind) | Shift accent from blue to red-orange |
| Monochrome | Map all hues to single accent; differentiate via tone |
User selects in Settings § Accessibility. Stored per-user.
R5 — High-contrast preset
Special preset (high_contrast in canonical 12) that locks AAA contrast on all role pairs. Auto-activated when:
- User toggles "High contrast" in Settings § Accessibility
- OS reports
prefers-contrast: more(Web, modern OS) - Forced colors active (Windows High Contrast mode)
Layered on top of any other L1-L3 customization (tightens, never loosens contrast).
R6 — Theme editor UI (advanced surface)
/playground/ on kds.koder.dev exposes a token paste editor today. Future "Theme Builder" (per #049.55) provides:
- Seed color picker → live preview of all 18 roles
- Per-role lock + override
- Save as canonical-shaped JSON
- Export buttons: CSS, SCSS, JSON, Dart, d.ts
- AAA gate visual indicator per pair (red if fails)
Lives in koderwebkit's KoderThemeBuilder widget (planned).
R7 — Forbidden customization
Even at L3, the following are *ixed*to preserve product identity + baseline a11y:
- Cannot disable AAA contrast gate (only
high_contrastpresetexists to TIGHTEN; never loosen)
- Cannot set
bg === textor any zero-contrast pair - Cannot remove a required role (only override its value)
- Cannot change role semantics (e.g., can't make
errormean"success" in your scheme — the role's meaning is fixed)
Cross-link
color-schemes.kmd— canonical 12 presetscolor-roles.kmd— role taxonomycolor-dynamic.kmd— L1 seed-based generationcustomization.kmd— overall customization axesfoundations/elements.kmd— which family uses which roles