Material 3 · ExpressiveWhite-label corev1.0 · Stable
One core. Every bank looks like itself.
A single Material 3 Expressive foundation that any bank can wear as its own — colour, shape, type and motion all flex to the brand, while the structure, accessibility and engineering stay identical. Switch the brand pills above and watch the entire system re-skin.
4
brand themes, infinite seeds
2
modes — light & dark
LTR / RTL
bidi-ready by default
M3
tokens map 1:1 to Flutter
Four banks, one system
Tap a card to theme everything below
Personalisation runs deeper than a primary colour. Each brand redefines its full tonal palette, a corner-radius family, a display typeface and motion feel — yet every component, flow and accessibility guarantee is shared.
Foundations
The tokens behind every screen
Colour roles · derived from one seed, re-mapped per brand & mode
{{ c.name }}
Type scale
{{ t.role }} · {{ t.px }}
{{ t.sample }}
Shape scale · a per-brand corner family
{{ s.name }}
Elevation
{{ e.name }}
Spacing · 4px base
{{ sp.n }}
Motion · easing & duration tokens
{{ m.name }}{{ m.dur }}
Spring easing is the Expressive default for spatial transitions; standard/emphasized cover the rest.
Components
A complete Expressive kit
Every control is live — toggle, drag and select. All of them inherit the active brand's colour, corner family and type, so a component built once is correct for every bank.
Real banking surfaces assembled entirely from the kit above. Switch the brand or mode at the top, or tap the bottom navigation — the structure never moves, only the skin.
Six rules decide every call in this system. When a decision is hard, the principle wins — not the individual screen.
{{ p.n }}
{{ p.title }}
{{ p.body }}
Interaction
States & focus, specified
Every interactive surface carries the M3 state-layer model — a translucent overlay of its on- colour. The ratios are fixed system-wide so a button in any brand responds with the same physics.
State layers · filled action
Pay{{ s.label }}{{ s.spec }}
Try itTab to either to see the focus ring.
Focus indicator
Pay
A 2px primary ring at 2px offset, keyboard-only via :focus-visible. Never shown on pointer input.
{{ n.iconEl }}{{ n.text }}
Motion
Three curves, one feel
Expressive motion is tokenised, not improvised. Standard for the everyday, emphasized for entrances, spring for anything that should feel alive. All three honour prefers-reduced-motion.
{{ m.name }}{{ m.dur }}
{{ m.use }}
{{ m.curve }}
Spacing
A 4-point grid, everywhere
Every gap and padding snaps to a multiple of four. Use gap on flex and grid rather than per-child margins, so spacing survives reorder and RTL untouched.
{{ s.px }}{{ s.use }}
Accessibility
AA is the floor, not the goal
Accessibility is built into the tokens, so it ships with every brand for free. The on- pairings are contrast-validated in every theme and mode before release.
Contrast pairs
{{ c.label }}{{ c.ratio }}
Touch targets · 48dp minimum
A 22px glyph still gets a 48×48 hit area. Icon buttons pad to target; they never shrink to fit.
{{ a.iconEl }}{{ a.text }}
Anatomy
Read one component to read them all
The transaction row is the system in miniature: a leading token-shaped container, a two-line text block, and tabular figures — every measure a token, nothing literal.
Twelve brand levers separate every bank. The rule: a brand must move at least six — so two banks never feel like the same app reskinned lazily, yet never like a different product either. {{ brandLeverSentence }}
{{ l.iconEl }}{{ l.label }}
Governance
Versioned, governed, ready to ship
Odyssey ships on semantic versioning. Tokens are the public API: a token rename is a breaking change, a new token is a minor, a value fix is a patch. Components carry a status so teams know what they can build on.
ComponentStatusSince
{{ r.name }}{{ r.status }}{{ r.since }}
Onboard a bank in one config
A new bank is one tenant config set — seed hue, corner family, type, motif — never a fork. Author it, validate light/dark + RTL + contrast, ship. The component and screen code never changes.