docs/design-tokens.json opens like this:
"description": "Poppy, fresh-produce-energy design system for the Munchable gut-health app. Machine-readable source of truth; codegen'd into the RN theme."
114 lines, 48 hex values in it, 40 of them distinct. The design doc beside it is firmer:
The machine-readable source of truth is
docs/design-tokens.json, which is codegen'd into the React Native theme. When the two disagree, the JSON wins and this doc is stale, fix it.
Open munchable.app and look at it. Cream background, cocoa text, caramel buttons. Then look at the JSON's palette: bg: #FBFDF6, primary: #0A8049. That is a white-green. The brand section calls for "vibrant greens, ripe fruit accents" and a farmers market on a sunny morning. The app has been a biscuit for months.
I compared the two palettes with a script instead of my eyes:
json hexes 40 package hexes 17
json ∩ package 1 -> {'#FFFFFF'}
One colour in common, and it is white.
How to tell which file is real
Not by what it says about itself. By who imports it.
rg -n "design-tokens.json" --glob '!node_modules'
# docs/DESIGN-SYSTEM.md:6 the machine-readable source of truth sentence
# docs/DESIGN-SYSTEM.md:298 a line in the future-work list
Two hits, both in the prose document that calls it the source of truth. No build step, no script, no app. Line 298 is the giveaway, because it is in a list of future work:
Consume
docs/design-tokens.jsonvia codegen into a typedthemeobject
So the codegen the sixth line describes in the present tense is item one of the roadmap at the bottom. The file was authored as the output of a design exercise, the exercise produced a direction that got replaced, and because nothing ever imported it, nothing ever noticed. Its $schema points at https://munchable.app/schemas/design-tokens.v1.json, which answers 404. I checked that too.
The one that is real
packages/design-tokens/src/index.ts, 151 lines, 17 hex values, no platform in it at all. Its header explains why it is a package rather than a file in the app:
These values live in their own package because more than one surface has to look like the app. The reels render a phone running Munchable, and they used to carry a hand-copied mirror of
apps/mobile/src/theme/tokens.ts: a copy drifts silently, so changing the caution colour or the "Can't assess" label in the app left the marketing videos showing the old one with no type error anywhere.
That is a package justified by an actual incident, not by symmetry. The marketing videos are Remotion compositions that render a phone running the app. When the app's colours moved, the videos kept rendering the old ones, and the only detector was somebody watching a video and squinting. Now both import the same object, and a renamed key is a type error in the video renderer.
Two consumers: the Expo app's theme module re-exports everything and adds what needs React Native (motion curves, shadows, the font family names Expo loads), and the video package's theme. One source, two surfaces, one of which is a video.
The colours themselves carry their contrast ratios in comments next to them:
bg: '#FFF8EC', // cream
text: '#3B2314', // cocoa · cream 13.9:1
textMuted: '#7F5F47', // warm brown · cream 5.5:1
and one of them carries a warning instead:
/** Pop colour (pink sprinkles). Decoration only: it fails contrast as text. */
accent: string;
A token whose doc comment says "do not put words on me" is doing more for accessibility than the audit that found it.
The third palette, which is the website
The site you are reading the links to is not a consumer of that package. app/globals.css declares 28 hex values of its own. 15 of them are the same strings as the package's, which is the brand surviving a copy by hand. 13 are the website's alone: deeper shades for hairlines and gradients, the kind of value a CSS file needs and a React Native StyleSheet does not.
So the honest count is three palettes: a fossil with no importers, a package with two, and a stylesheet that agrees with the package 15 times out of 28 because somebody was careful. You can see the result of the careful part on /recipes and /conditions, where the cards and the app's cards are the same cream and the same border.
The typography is half alive
This is the part I did not expect. The fossil's font families are still the app's font families: Outfit_700Bold for headings, Nunito_400Regular for body. Every size in it is wrong:
| JSON | shipping | |
|---|---|---|
| display | 40 / 44 | 32 / 38 |
| h1 | 32 / 38 | 27 / 33 |
| h2 | 26 / 32 | 21 / 27 |
| title | 20 / 26 | 17 / 23 |
Everything shrank, and a face the JSON has never heard of (Fredoka, for the wordmark and hero headlines only) was added. So the dead file is not uniformly dead. It is right about two font names and wrong about every number next to them, which is the worst possible state for a document people are told to trust when it disagrees.
What bypasses the palette
23 hex literals sit in the app's own source, outside the tokens. I expected sloppiness and found a pattern: almost all of them are in the camera chrome and in the SVG of the cookie mark.
The camera ones exist because the palette has no dark values. Munchable is light mode only, deliberately, and a viewfinder is the one screen that is mostly a dark photograph, so the scrims and the hairlines over it need colours the design system does not name. The cookie mark's are artwork, four flat fills inside one SVG that happen to be brand colours, which is a different kind of value from a semantic token.
Neither is an argument for leaving them there. Both are an argument that the tokens file is missing two named groups, which is a more useful conclusion than "developers hardcode colours".
The rule I would write down instead
A file is a source of truth when something breaks if it is wrong. The JSON could be deleted today and the app, the website and the videos would all render identically, which is the definition of not being one. The package cannot be edited without changing both the app and the videos, which is the definition of being one.
And the tie-break sentence in the doc, "when the two disagree, the JSON wins", is now the most dangerous line in the repository: a rule that points a future contributor at the green.
Compare for yourself. The app's palette is on munchable.app, and the 26-pixel cookie mark every one of those colours was lifted from is the first thing in the header on every page.
Top comments (0)