Research note
Design tokens that survive three platforms
One source of truth emitting web, Kotlin, and SwiftUI. Where generated code holds up, and the exact point where it stops being worth it.
The v3 UI Kit makes a specific promise: theme, components, patterns, and utilities ship as copyable code for web, Android, and iOS, generated from one set of tokens. This note is about how far that promise stretches before it breaks, because it does break, and knowing where has been more useful than pretending otherwise.
Tokens are not variables
The first version of any design system stores colours and spacings as named values and calls it a token system. It is not, and the difference shows up the moment you need a second platform.
A token is a semantic commitment. --color-primary is not "ink navy", it is "the colour of structure and chrome", and it happens to currently be #1A2F45. The distinction sounds academic until you generate a dark theme, at which point every token whose name describes its appearance rather than its role becomes a lie you have to work around.
The Forge palette is defined by role for exactly this reason:
| Token | Value | Role |
|---|---|---|
| primary | #1A2F45 | Chrome, headings, structure |
| secondary | #0E9F90 | Secondary actions, focus |
| accent | #E4572E | Emphasis, destructive, highlights |
| highlight | #B08D3A | Rare chrome accent, toolkit only |
Naming by role is what makes a single definition emit sensibly into three languages. Naming by appearance is what makes you maintain three definitions that drift.
Where generation works, and where it stops
We have run this across enough components to be fairly confident about the boundary, and it is sharper than we expected.
- Generates cleanly: anything whose entire definition is tokens plus states. Buttons, badges, inputs, cards, chips. The platform differences are syntax, and syntax is exactly what a generator is good at.
- Generates with caveats: layout primitives. Flex and stack models are conceptually similar across the three targets and differ in ways that need a human decision, most often about how overflow behaves.
- Does not generate usefully: anything with platform-native interaction expectations. Data tables, date pickers, navigation. A generated iOS date picker is not a date picker an iOS user recognises, and shipping one is worse than shipping nothing.
The dividing line: generation works up to the point where a component carries platform convention. Past that, the tokens should still apply, and the structure should be written by someone who knows the platform.
This is why the kit's templates are treated differently from its components. Templates are full live websites you can look at and take patterns from, not a source dump to generate from, because a whole page is mostly convention and composition, which is precisely the part generation is bad at.
The playground exists because reading is not enough
Documentation for a design system has a specific failure: developers do not read it, they copy from it and then modify. So the interesting question is not whether the docs explain the token, it is whether the thing they copied still works after they change it.
Putting the playground last in the navigation, after theme, components, patterns, utilities, and templates, was deliberate. It is where you paste something in, change it, and see immediately whether it survived. Nearly every gap we have found in the token system was found by someone modifying generated code in the playground and watching it come apart.
The open question
We keep testing how much of a component can come out of tokens before a human has to intervene, and the honest answer is that the boundary moves depending on how much convention the component carries, not on how complex it is. A complex component with no platform expectations generates fine. A simple one that users have opinions about does not.
Mapping that boundary properly, per target, is on the open problems list.