Material 3 → Verge
A practical, opinionated guide for moving an interface from Google's Material 3 to Verge, the canonical visual language of the Koder Design System. Token rename, component swap, and the few philosophical differences worth knowing. Verge v0 is Adwaita-based, not Material-based — most M3 tokens have a Verge counterpart but the visual language differs.
Why migrate
- Self-hosted — Koder Design ships from your own infrastructure — no Google CDN, no third-party fetch, no version pinning on someone else's release cycle.
- Full theme control — Light and dark are the baseline; 35 preset themes ship out of the box, and you can override every token at three customisation levels (L0 brand-only → L4 token-level).
- 35 presets — Era, Brand, Mood, Cultural, Domain families — pick or compose. Material You is one personalisation strategy; Koder treats it as one variant of many.
- Open spec — Every page on this site is generated from a spec under `meta/docs/stack/specs/`. Fork the spec, regenerate the design system — no black box.
- No tracking — Koder Design's docs site uses no analytics scripts, no per-page beacon. The reporter widget at the bottom is opt-in and anonymous.
Token mapping
Most Material 3 colour, shape, motion, and typography tokens have a 1:1 Koder counterpart. Where the philosophy differs, the table calls out the rationale.
| Material 3 token | Koder Design token | Notes |
|---|---|---|
md.sys.color.primary | --kdr-accent | 1:1 rename |
md.sys.color.on-primary | --kdr-accent-on | 1:1 rename |
md.sys.color.primary-container | --kdr-surface-2 | Koder collapses Material's container hierarchy to two tiers (`surface` / `surface-2`) |
md.sys.color.primary-fixed | --kdr-accent | Koder doesn't model the fixed-tone variants — use the base role |
md.sys.color.surface | --kdr-surface | 1:1 rename |
md.sys.color.surface-container | --kdr-surface-2 | Koder collapses Material's container hierarchy to two tiers (`surface` / `surface-2`) |
md.sys.color.on-surface | --kdr-text | 1:1 rename |
md.sys.color.on-surface-variant | --kdr-text-muted | 1:1 rename |
md.sys.color.outline | --kdr-border | 1:1 rename |
md.sys.color.error | --kdr-error | 1:1 rename |
md.sys.elevation.level0..5 | --kdr-shadow-0..5 | Tonal recipe differs; see `specs/themes/elevation.kmd` |
md.sys.shape.corner.small | --kdr-radius-sm | Koder ships sm/md/lg only; Material's 5-tier scale collapses |
md.sys.shape.corner.medium | --kdr-radius-md | Koder ships sm/md/lg only; Material's 5-tier scale collapses |
md.sys.shape.corner.large | --kdr-radius-lg | Koder ships sm/md/lg only; Material's 5-tier scale collapses |
md.sys.typescale.display-large | var(--kds-font-display) | Koder uses 4 role-based families (sans/mono/display/serif); Material's per-class scale maps via role |
md.sys.typescale.body-large | var(--kds-font-sans) | Koder uses 4 role-based families (sans/mono/display/serif); Material's per-class scale maps via role |
md.sys.motion.easing.standard | cubic-bezier(0.2, 0.8, 0.2, 1) | Koder ships canonical curves inline (no `--kdr-motion-*` vars yet — tracked for v2) |
md.sys.motion.duration.medium2 | 240ms | Koder ships canonical curves inline (no `--kdr-motion-*` vars yet — tracked for v2) |
md.sys.state.hover.opacity | n/a | Koder uses background-color shifts (surface-2, accent-mix) instead of Material's stateful overlay opacities |
Component mapping
Each Koder component is one spec that consolidates the Material variants under a single API (Material agrupa = nós agrupamos).
| Material 3 component | Koder Design component | Spec |
|---|---|---|
MaterialButton | KoderButton | components/buttons |
FilledButton / TonalButton / OutlinedButton / TextButton / FAB | KoderButton via `variant=` prop | components/buttons |
Card (3 variants) | KoderCard | components/cards |
Checkbox | KoderCheckbox | components/checkbox |
Chip (4 variants) | KoderChip | components/chips |
Dialog / AlertDialog | KoderDialog | components/dialogs |
Menu / DropdownMenu | KoderMenu | components/menus |
NavigationBar / NavigationRail / NavigationDrawer | KoderNav via `variant=` prop | components/navigation |
Snackbar | KoderSnackbar | components/snackbars |
Switch | KoderSwitch | components/switch |
Tabs (primary / secondary) | KoderTabs | components/tabs |
TextField (filled / outlined) | KoderTextField | components/text-fields |
Tooltip (plain / rich) | KoderTooltip | components/tooltips |
DatePicker / TimePicker | KoderDatePicker / KoderTimePicker | components/pickers |
BottomSheet / SideSheet | KoderSheet | components/sheets |
Breaking differences
- 13-tone palette → 18-role taxonomy. Material derives 13 tones per key colour and exposes them as roles. Koder skips the intermediate tone step — its 18 semantic roles are mapped to HCT-derived seed colours directly. Practical effect: when you need a colour, ask for the role (`accent-on`, `surface-2`), not the tone.
- Material You → presets + seed-color. Material You re-derives the system from the user's wallpaper on Android. Koder Design treats this as one of 35+ preset strategies and offers an explicit seed-color customisation level (L2). Cross-platform parity matters more than wallpaper extraction.
- Tonal elevation only at dark. Material 3 uses tonal elevation in both schemes. Koder uses shadow elevation in light mode and tonal overlay in dark mode — combined where contrast demands it. See `specs/themes/elevation.kmd`.
- State overlay opacity → background swap. Material applies a translucent overlay (`hover.opacity = 0.08`, etc.) on top of the base colour. Koder swaps to a different surface token at the same role. Cleaner cascade, simpler theming, less translucency stack to debug.
- Single sans default, opt-in serif. Material 3 ships Roboto Flex as a variable font by default. Koder ships Inter (sans) + JetBrains Mono (mono) — self-hosted WOFF2. Serif is opt-in for content-heavy surfaces (e.g., reading mode); display fonts are commissioned per preset.
Stepwise upgrade
- Audit your current token surface.
Run `grep -r 'md.sys' src/` to enumerate every Material token you reference. Map each to its Koder equivalent using the table above. Leave the rest at default for the first pass — opinionated migrations get into trouble when you try to translate everything at once.
- Swap the theme provider.
Replace your `MaterialApp` / `MaterialTheme` wrapper with Koder Design's `KoderApp` (Flutter) or `<koder-design>` (Web). Either accepts a `preset=` prop (default: `koder-base`) plus optional `seedColor` for L2 customisation.
- Rename component imports.
Use the component table above. Most renames are mechanical (`MaterialButton` → `KoderButton`); a few collapse multiple Material widgets into a single Koder one via a `variant=` prop (button styles, nav surfaces, dialog types).
- Tune the L2 seed-color.
Open `/playground/` and paste your brand seed. The 18-role palette derives automatically. If you need finer control (L3 / L4), edit your token override file directly — the playground emits the JSON for you.
- Verify with the contrast checker.
Pop /tools/contrast/ to spot-check each token pair against WCAG 2.2 AA / AAA. Pay special attention to the `text-muted` × `surface-2` pair — Material's tone scale was permissive here; Koder's defaults are tighter but worth verifying when you bring in a brand colour.
Known gaps
These are Material features Koder Design does not yet match. Tracked publicly:
- Wallpaper-derived dynamic colour (Material You on Android). Koder offers seed-color customisation but not OS-level wallpaper extraction. Tracked: `themes/color-dynamic.kmd` v2.
- Variable-axis font with grade + optical-size axes (Material Symbols, Roboto Flex). Koder ships Inter + JetBrains Mono — single weight axis. Koder Display custom commission tracked in `projects/koder-stack#128`.
- Figma kit + plugin parity. Spec exists (`specs/tools/design-kit-export.kmd`); implementation is the slowest of the 5 tool tracks.
- Per-platform code samples on every component page (Flutter / Web / Android Compose / iOS SwiftUI). Spec at `specs/develop/code-samples-toggle.kmd`.
Watch progress on the Koder Design release blog →