Flint: one design system for web and iOS, checked on every build
The design system under AccessSpark and Inner Spark Media. One token source feeds CSS, MUI, shadcn/ui, and SwiftUI, and every build proves they match and that text clears 7:1.
The context
I run several products on my own: AccessSpark (a React app on MUI), its marketing site (Astro), the Inner Spark Media site (EJS), and two iOS apps. Each one needed the same blue, the same type, the same focus ring.
Every product had its own copy of the colors. Copies drift. A blue got nudged in one place and not the others. The iOS apps carried hand-typed hex values. A stray release tag made the token repo look like a 2.x release when it was on 1.8. Nothing broke loudly. It just slowly stopped matching.
For an accessibility company, drift is worse. A color that passed contrast in one product could fail in another, and nobody would know until an audit found it.
What I owned
All of it. This is my system for my own products, so I was the designer, the token architect, the build engineer, and the docs writer. The Figma library and the code both come from the same decisions, and when they disagree the repo wins.
Token architecture
Primitive ladders, semantic tokens for light and dark, and a component tier. Naming that reads the same in CSS, JavaScript, and Swift.
Build and checks
The build that turns one file into every platform's format, plus the contrast, parity, and page checks that gate each release.
Docs and workbench
flint.innersparkmedia.com: getting started per platform, foundations, component guidance, and a live workbench.
Figma library
Variables and components that mirror the tokens, so design and code start from the same values.
The constraints
One person maintains it, around a full-time job. That rules out anything that needs a team to maintain. If a rule matters, a script has to enforce it.
Four platforms with different ideas of what a theme is. CSS wants variables. MUI wants a theme object. shadcn/ui wants Tailwind variables with its own names. SwiftUI wants a Swift package. The products could not be rewritten to fit the system. The system had to fit them.
The decisions
Tokens in code are the source, Figma follows. The common setup runs the other way: designers own variables in Figma and export them. I rejected that. My products ship from code, a script can check code, and a script cannot check a Figma file. The Figma library mirrors tokens.json, and the docs say plainly that the repo wins.
Three tiers, and apps never touch the bottom one. Primitives are the color ladders. Semantic tokens name a job, like text.muted or action.primary, and point at a ladder step for light and another for dark. Component tokens point at semantic ones. I rejected a flat list of semantic hex values per theme. That is how the drift started: two themes, typed twice, edited once.
Text at 7:1. WCAG AA asks for 4.5:1. I set Flint's bar at 7:1 (AAA) for every text pairing and 3:1 for control edges, focus rings, and chart marks. The rejected option was AA with a buffer "where it matters." That turns into a judgment call on every screen. One hard number is something a script can enforce, and an accessibility company should not ship the minimum.
Bridge to shadcn/ui, keep MUI. AccessSpark runs on MUI. I looked hard at moving it to shadcn/ui and rejected a rewrite. Instead Flint ships a shadcn bridge, one CSS file that maps every shadcn variable onto Flint, so new work can use shadcn and AccessSpark keeps working. Same tokens, both libraries.
Build my own workbench, drop Storybook. I first built the component workbench in Storybook. Then I audited the live docs with AccessSpark. Flint's components passed. Storybook's own interface did not: missing landmarks, empty button names, removed focus outlines, and I could not fix any of it without forking Storybook. A design system that sells accessibility cannot publish a test bench that fails it. I rebuilt the workbench inside the docs site in about a day.
What shipped
One package, @innerspark/flint, built from one tokens.json. A build turns it into CSS variables, typed JavaScript, an MUI theme, a shadcn bridge, a Swift package with SwiftUI and UIKit colors, and a DTCG file for other tools. Then it checks itself. A release cannot ship if any gate fails.
The outcome
I pointed AccessSpark at the live docs. The first crawl found real problems in how I had built the site and in Storybook. I fixed mine, replaced Storybook, and crawled again.
The audit also went the other way. Five of AccessSpark's findings were wrong: a rule that only read inline CSS, advice to turn a toggle button into a link, and a rollup that blamed the whole site for Storybook's problems. Those are AccessSpark bugs to fix.
What I'd carry forward
Every rule I care about fails the build when it breaks: 7:1 text, matching platforms, accessible pages. The stuff I left to memory is what drifted.
Audit your own tools too. The workbench existed to catch problems in components, and it turned out to be a problem itself. I only saw it because I ran the same audit on the test bench that I run on the product.
There is more to do, and I would rather say so. The MUI theme does not apply every component token yet (ASDS-44). This portfolio still carries a hand-copied set of Flint colors, which is exactly the drift Flint exists to stop, and moving it onto the package is next.