LOKEDESIGN SYSTEM

One design system.
Multiple renderers.

There is no Flutter design system and no web design system. There is one system — colour, type, spacing, shape, motion — and two renderers that draw it. Naming the halves by platform is what makes them drift.

loke.tokens.jsoncanonical source
tokens.dartloke_flutter · mobile
loke-tokens.cssloke-design-system · web
tokens.cssmarketing · tokens only

Generated, never transcribed. Every consumer vendors a copy and gates on drift; hand-editing a generated file is a red build, not a slow divergence.

The shared contract

What every renderer agrees to. These are the rules that survive a platform change — the parts that are genuinely one system rather than two implementations that happen to match today.

Three layers

Primitives read tokens only and change almost never. Patterns read tokens and primitives. Domain reads anything and changes constantly.

The lower two must never import a model or a service. That single constraint is what keeps them reusable instead of slowly accreting product knowledge — and it is enforced by a script, not a doc comment.

LayerFlutterWeb
Primitiveslib/ui/src/components/ui/
Patternslib/patterns/src/components/loke/
Domainlib/widgets/src/components/app/

Components read semantic roles

A component asks for surface-raised, never neutral-800. Reaching past a role into a ramp position is the thing that makes a theme swap impossible, so it is a bug even when it looks right.

Roles are the same nine names on both platforms. The ramps behind them are an implementation detail of the theme.

Type roles carry no size

A role is family + weight + case + tracking. Size is an independent axis passed at the call site, so a new size is a parameter rather than a new token.

Tracking is stored in em and multiplied by size, so letter-spacing stays proportional instead of being hand-tuned per constant.

Marketing shares tokens, not components

Colour, type, logo and icon family are shared. The component set is not, and that is deliberate.

Product UI optimises for density and repetition — the same row, nine hundred times, has to be quiet. Marketing optimises for expression and the one-off. Force one component set on both and marketing pages start to look like a settings screen, so marketing quietly stops using the system at all.

Galleries

The galleries are necessarily separate — Flutter renders Flutter, React renders React. They show the same system drawn twice, from the same tokens.

Tokens

Generated from loke.tokens.json at build time. If a value here is wrong, the source is wrong — there is nowhere else to fix it.

Brand

brand.red#FD4A46
brand.green#08BF0D
brand.blue#3965FF
brand.gold#FFB800

Ramps

12 hues × 5 stops. Five steps, not ten — a ramp you can hold in your head.

red

wash#FFEFED
subtle#FFBFB7
base#FD4A46
strong#CD2026
deep#9C0011

orange

wash#FFF0E9
subtle#FFC1A2
base#FC7922
strong#B95200
deep#893B00

gold

wash#FCF2E2
subtle#F7D08E
base#FFB800
strong#976C00
deep#704E00

lime

wash#ECF8E2
subtle#C3F297
base#99EC09
strong#548500
deep#3C6200

green

wash#E7FAE5
subtle#9EE798
base#08BF0D
strong#008E03
deep#006902

teal

wash#E6F9F0
subtle#96E4C5
base#06DBA5
strong#008966
deep#00654A

cyan

wash#E5F8F9
subtle#9FEEF3
base#20E9F3
strong#00858B
deep#006266

sky

wash#EAF5FF
subtle#A8D7FF
base#3DABFA
strong#007ABF
deep#005A8E

blue

wash#EEF3FF
subtle#BCD1FF
base#3965FF
strong#1F3FCE
deep#0E19A0

violet

wash#F2F2FF
subtle#CDCAFF
base#692FEE
strong#4A00B5
deep#2C0073

purple

wash#FCEEFF
subtle#F4B8FF
base#C416DF
strong#9200A7
deep#660075

pink

wash#FFEEF4
subtle#FFBAD3
base#F73B99
strong#C70175
deep#930055

Neutral

Cool-tinted, so greys sit under the brand red without going muddy.

0#FFFFFF
50#F8FAFD
100#F0F3F6
200#E2E5EA
300#CCD0D5
400#ADB1B7
500#8E9399
600#70747A
700#5A5E63
800#3A3D42
850#2B2E32
900#1E2124
925#1C1C1C
950#090A0D

Semantic roles

What components actually read. Two themes, one set of names.

RoleLightDark
bg #FFFFFF #1C1C1C
surface #F8FAFD #1E2124
surface-raised #FFFFFF #3A3D42
surface-sunken #E2E5EA #090A0D
field #F0F3F6 #3A3D42
text #1E2124 #FFFFFF
text-secondary #5A5E63 #ADB1B7
text-tertiary #70747A #8E9399
text-inverse #FFFFFF #1C1C1C
border #E2E5EA #3A3D42
border-strong #CCD0D5 #5A5E63
disabled #ADB1B7 #70747A

Type roles

RoleFamilyWeightCaseTrackingFigures
display display 900 UPPER 0
title sans 700 UPPER 0.02em
button sans 900 UPPER 0.04em
label sans 700 UPPER 0.04em
body sans 400 sentence 0
bodyStrong sans 500 sentence 0
numeric mono 500 sentence 0 tabular
numericStrong mono 700 sentence 0 tabular
meta mono 500 UPPER 0.06em tabular

Scale & shape

Spacing

space-00px
space-14px
space-28px
space-312px
space-416px
space-520px
space-624px
space-728px
space-832px
space-1040px
space-1248px
space-1664px
space-2080px
space-2496px

Radius

radius-none0px
radius-xs2px
radius-overlay20px
radius-full9999px

Border

border-sm2px
border3px
border-lg4px

Duration

dur-fast90ms
dur-base260ms
dur-bounce440ms

Shadow

Hard offset, always. The one exception is mediaLegibility — over unpredictable video a hard offset reads as a UI glitch, so a soft shadow is the legible choice there and nowhere else.

hard
hardSm
accent

Icons

System and gamification both use Material Symbols Sharp-Fill. Sharp and filled, to match square corners and solid fills.