Color roles
Semantic color role taxonomy — the mapping from concrete tokens (bg, surface, accent, error, etc.) to the UI elements that use each. Material parity (`/styles/color/roles`). Companion to `themes/color-schemes.kmd` (which defines the actual colors) and `foundations/elements.kmd` (which says which family uses which role).
Facet Visual do Koder Design. Material parity: https://m3.material.io/styles/color/roles.
A role is a semantic slot ("primary action accent", "page background"). A token is the concrete value bound to that role in the current theme. Roles stay constant across themes; tokens vary.
R1 — The 18 canonical roles
Organized by the 5 element families from foundations/elements.kmd:
Surface roles
| Role | Token | Use |
|---|---|---|
bg |
--kdr-bg |
Page background |
surface |
--kdr-surface |
Cards, sheets, raised containers |
surface-variant |
--kdr-surface-2 |
Recessed/inset surfaces (search box, code block) |
surface-inverse |
inverse-bg | Snackbars, tooltips (dark on light pages, light on dark) |
Content roles
| Role | Token | Use |
|---|---|---|
text |
--kdr-text |
Primary text on bg / surface |
text-muted |
--kdr-text-muted |
Secondary text, captions, helper text |
text-subtle |
--kdr-text-subtle |
Disabled text, placeholders |
text-on-accent |
--kdr-accent-on |
Text on accent-filled surfaces |
Control roles
| Role | Token | Use |
|---|---|---|
accent |
--kdr-accent |
Primary action button, active link |
accent-strong |
--kdr-accent-strong |
Hover/pressed state of accent |
accent-on |
--kdr-accent-on |
Foreground on accent (text/icon) |
accent-tint |
accent at 12% opacity | Selected row background, focus halo |
Status roles
| Role | Token | Use |
|---|---|---|
error |
--kdr-error |
Error state border/text |
error-bg |
--kdr-error-bg |
Error banner background (subtle tint) |
warning |
--kdr-warning |
Warning state border/text |
success |
--kdr-success |
Confirmation badge, valid input |
info |
--kdr-info |
Informational banner |
Decoration roles
| Role | Token | Use |
|---|---|---|
border |
--kdr-border |
Container borders, dividers |
border-strong |
--kdr-border-strong |
Emphasis borders (focused container) |
focus |
--kdr-focus |
Keyboard focus ring color |
selection |
--kdr-selection |
Text selection bg / row selection bg |
R2 — Role binding contract
Every koder_kit widget MUST bind colors via roles, not raw hex:
// ❌
Container(color: Color(0xFF3B5BFD)) // hardcoded
// ✅
Container(color: KoderTheme.of(context).accent)The theme exposes a typed KoderColorRoles struct (planned) with one getter per role. Widget code never references tokens by string.
R3 — Inversion in dark mode
Roles flip meaning between light and dark themes via the token system, not via per-widget logic. Example:
| Role | Light theme step | Dark theme step |
|---|---|---|
bg |
white / slate-50 |
slate-900 |
text |
slate-900 |
slate-50 |
accent |
blue-600 |
blue-400 (lighter for dark bg legibility) |
Steps reference themes/palette.kmd (primitive tier) — never raw hex. Widget code stays the same; the theme rebinds the tokens. The full default binding is R7.
R4 — Contrast requirements
Per color-schemes.kmd § AAA contrast, role bindings must satisfy:
| Pair | Min ratio |
|---|---|
text/bg, text/surface |
7.0 (AAA Normal) |
text-muted/bg |
4.5 (AA) |
accent-on/accent |
4.5 (AA) |
error/error-bg |
4.5 |
focus/bg |
3.0 (UI element contrast) |
high_contrast preset tightens all to ≥ 7.0.
R5 — Forbidden bindings
- ❌ Body text on
accentbackground (useaccent-oninstead) - ❌
text-mutedfor primary action labels (usetextoraccent-on) - ❌ Body text in
errorcolor (red text outside error context is alarming) - ❌ Custom hex on user-facing surface without going through a role
R6 — When to add a new role
Adding a role is an architectural decision (impacts every theme). Threshold:
- Needed by ≥ 2 widgets
- Not expressible via existing role + opacity modifier
- Has a stable semantic meaning across themes
Process: propose via PR with the binding added to all 12 color scheme presets simultaneously (per color-schemes.kmd Catalog).
R7 — Default primitive binding (Verge base theme)
Ratified 2026-06-03 (koder-design#039). Each role resolves to a named step from
themes/palette.kmd(the primitive tier) — this is what "never raw hex" (R2/R5) means concretely. Presets MAY override (a preset is just a different binding); this is the canonical default for the Verge base theme. Steps map 1:1 tometa/brand/koder-design/palette/primitives/primitives.json, so the binding is machine-checkable (contrast lives there too — R4).
| Role | Light | Dark |
|---|---|---|
bg |
white |
slate-900 |
surface |
white |
slate-800 |
surface-variant |
slate-100 |
slate-700 |
surface-inverse |
slate-900 |
slate-50 |
text |
slate-900 |
slate-50 |
text-muted |
slate-600 |
slate-300 |
text-subtle |
slate-400 |
slate-500 |
text-on-accent |
white |
slate-900 |
accent |
blue-600 |
blue-400 |
accent-strong |
blue-700 |
blue-300 |
accent-on |
white |
slate-900 |
accent-tint |
blue-50 |
blue-900 |
error |
red-600 |
red-400 |
error-bg |
red-50 |
red-900 |
warning |
amber-700 |
amber-400 |
success |
green-600 |
green-400 |
info |
blue-600 |
blue-400 |
border |
slate-200 |
slate-700 |
border-strong |
slate-300 |
slate-600 |
focus |
blue-500 |
blue-400 |
selection |
blue-100 |
blue-900 |
Notes:
accentresolves toblue-600(not the brand fillblue-500/Verge#3584E4) because the role is used for text/links on light, which must clear AA (≥4.5 on white);blue-600does,blue-500does not. A pure accent fill withaccent-ontext may useblue-500.- The Verge v1 OLED-aware dark variant (
verge.kmd §R4.3) overridesbg/surfaceto its near-black ramp (#0A0A0B…) instead ofslate-900; the rest of the dark column holds.slate-900is the palette-default floor for non-OLED dark presets. high_contrastpreset (R4) pushes every pair to ≥7.0 by shifting tothe extreme steps (
slate-900white,blue-700blue-300).
Cross-link
themes/palette.kmd— primitive steps every role binds to (R7)
color-schemes.kmd— concrete tokens per presetlight-dark.kmd— light/dark switching behaviorfoundations/elements.kmd— which family uses which rolesinteraction/states.kmd— overlay/state colors derive from roles