
“Conventions are not documented. They are enforced.”
Five problems we kept running into, and the system that answers them
Eleven projects taught us the same five failures. CDS is what we built so we stop paying for them.
Across eleven projects we hit the same five failures. Web and mobile start identical and share nothing but a name two years later. Figma is one source of truth and the code is another, and nobody knows which is correct. Conventions live in a README and are followed until someone is busy. Every new product starts its component layer from zero. And any product sold under someone else’s brand either has tokens or it has thirty unmaintainable copies.
CDS answers all five. Design tokens arrive from Figma automatically over an MCP server — tokens, not components. A theme token resolves per platform — a CSS variable on web, a sentinel the native resolver replaces — so the platform split lives one layer below the call site instead of leaking into it. And the conventions are not documented, they are enforced: ~2,450 lines of verification that fails the build.
74 components, 430 tests, an 81-page catalog generated from source.
The problem
Platform.select puts a branch wherever it’s convenient; conditional imports let any file diverge. Neither leaves a trace, and nothing fails when they multiply.The constraint
What we did
Pushed the platform split one layer below the call site
createTheme()turns a token into a string that resolves per platform, so${theme.color.fg.default}behaves identically everywhere. Shared recipes sit above it, applied by both platforms, differing only in the trigger —:hoveragainstpressed. There is one definition of a color, so colors cannot diverge.Login call byte-identical on 3 targetsBrought tokens from Figma automatically
Over an MCP server, with a manual path as fallback. The token layer is a package —
tokensfeedscore, which feedsui— so a change has one entry point rather than a search-and-replace. This covers tokens, not components: palettes, scales and theme values flow from Figma, components are written in code.Tokens, not componentsGave the invariants an owner that fails the build
verify-tokens(902 lines),verify-conventions(1,056) andverify-katalog(485), plus an ESLint plugin with four rules. One rule carries the architecture: a pattern must not have a.nativevariant. CI calls that chain plus three real builds — Next, Vite and Metro — becausenext buildis the only place the'use client'rule is actually verified.~2,450 lines of verificationShipped it as a package already in use
In our financial platform — and in this site. Coverage is a full set rather than a starter kit: primitives, forms, overlays, navigation, data. Measured:
sideEffects: falsetook an entry importing onlyBoxfrom 253,075 B to 241,309 B. The trap is documented next to it —@cds/tokensmust not be marked false, orreset.cssis dropped and the layout collapses without a single error.253,075 B → 241,309 BLet no component know its own color
Only a token name. A new brand is a new set of values, not new code. That was proven on an earlier product carrying more than thirty white labels; here it is the architecture from the first commit rather than a rescue.
30+ labels, no new code
Box, Button, Card, Input and Text — the split happened in the primitives. The call is identical; the wrapper stays platform-specific.Leaves translating a single platform element and nothing more. This is where a split belongs: mechanical, contained and invisible from above.
div to View, input to TextInput — and nothing more.The result
What we’d do differently
We reviewed all 74 components and found 188 issues, which reduced to three root causes — and every one of them passes the entire toolchain. That review should have happened at 30 components, not 74: the same causes were already present and would have been an order of magnitude cheaper to correct. All 188 are fixed; aria-label and tabindex coverage is still open.
The method and the three causes are written up in a separate blog post.
- TypeScript
- Next.js 16
- React Native 0.87
- Vite
- styled-components
- pnpm workspaces
- Figma MCP
- custom ESLint plugin