DEV Community

Cover image for How to Sync Design Tokens Between React and Flutter (Without Losing Your Mind)
rahul patwa
rahul patwa

Posted on

How to Sync Design Tokens Between React and Flutter (Without Losing Your Mind)

Style Dictionary's Flutter support has been broken for years. I built tokensync — a CLI that generates CSS and Flutter ThemeData from one tokens.json file, then verifies they match numerically.
Your designer just updated the brand color.

You open your CSS file. Update --color-brand-500. Then you open your Flutter file. Update AppTheme._lightColors.primary. Then you grep for anywhere else it might appear. Then you do the same for dark mode. Then you hope you got them all.

Three weeks later a designer screenshots both apps side by side. The web button is #5C6BC0. The mobile button is #5B6BC0.

Off by one digit. Nobody noticed. It shipped.

If you maintain both a React web app and a Flutter mobile app, this is a design token sync problem — and it costs teams 6–20 hours every time tokens change.


Why existing design token tools don't solve this

Style Dictionary is the industry standard for design token transformation. It's genuinely great for CSS. But its Flutter support produces flat key-value constants — not the ThemeData, ColorScheme, or TextTheme that Flutter apps actually need:

// What Style Dictionary gives you for Flutter:
static const double typographyDisplayFontSize = 48.0;
static const double typographyDisplayFontWeight = 700;
// That's it. You still write TextStyle yourself.
Enter fullscreen mode Exit fullscreen mode

GitHub issues requesting proper Flutter support have been open since 2022 with no resolution. Most teams end up writing a custom script. It works until the first rebrand, then breaks.

The other pain point is letter-spacing conversion. CSS letter-spacing: -0.02em at font-size: 48px should become Flutter letterSpacing: -0.96. Every team figures this out independently. Every team gets it wrong at least once.


Introducing tokensync: one source of truth for React and Flutter design tokens

tokensync is an open-source CLI that reads a single tokens.json file in W3C DTCG format and generates:

  • tokens.css — CSS custom properties with :root (light) and [data-theme="dark"] (dark) selectors
  • app_theme.dart — complete Flutter ThemeData, ColorScheme, TextTheme, and TextStyle
  • tokens.ts — typed TypeScript constants for shared logic

Then it runs a parity check — numerically comparing every token value between the CSS and Dart outputs to catch drift before it ships.

npx tokensync init   # scaffold config + tokens.json
npx tokensync build  # generate all platform outputs
npx tokensync check  # verify React and Flutter values match
Enter fullscreen mode Exit fullscreen mode

What the generated Flutter output looks like

Given this design token:

{
  "typography": {
    "display": {
      "$type": "typography",
      "$value": {
        "fontFamily": "Inter",
        "fontSize": "48px",
        "fontWeight": 700,
        "lineHeight": "1.1",
        "letterSpacing": "-0.02em"
      }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

tokensync generates idiomatic Flutter ThemeData:

static const TextTheme _textTheme = TextTheme(
  displayLarge: TextStyle(
    fontFamily:    'Inter',
    fontSize:      48.0,
    fontWeight:    FontWeight.w700,
    height:        1.1,
    letterSpacing: -0.96,   // ← -0.02em × 48px, correctly converted
  ),
);
Enter fullscreen mode Exit fullscreen mode

And the matching CSS:

:root {
  --typography-display-font-family: Inter;
  --typography-display-font-size: 48px;
  --typography-display-font-weight: 700;
  --typography-display-line-height: 1.1;
  --typography-display-letter-spacing: -0.02em;
}
Enter fullscreen mode Exit fullscreen mode

The generated Dart file passes dart analyze with zero errors out of the box.


How the parity checker works

After building, tokensync compares every token value between platforms:

Parity: web vs flutter
11 tokens  ·  11 passed
✓ Parity check passed
Enter fullscreen mode Exit fullscreen mode

It normalizes values before comparing: em to px, named font weights ("SemiBold"600), 0em and 0.0 both treated as zero. If anything diverges, it reports the exact token name and delta.

For CI, add this to your pipeline:

tokensync check --ci   # exits with code 1 if CSS and Dart diverge
Enter fullscreen mode Exit fullscreen mode

Mismatches block merges. No more #5C6BC0 vs #5B6BC0 in production.


Dark mode with a single token definition

Define your semantic tokens with light and dark values:

{
  "semantic": {
    "$modes": {
      "light": { "color": { "primary": { "$value": "{color.brand.500}" } } },
      "dark":  { "color": { "primary": { "$value": "{color.brand.100}" } } }
    },
    "color": {
      "primary": { "$type": "color", "$value": "{color.brand.500}" }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

tokensync generates both :root / [data-theme="dark"] blocks in CSS and both ThemeData.light() / ThemeData.dark() in Flutter — from the same source of truth.


Figma integration

If your design tokens live in Figma Variables (Professional plan):

FIGMA_ACCESS_TOKEN=your_token FIGMA_FILE_KEY=your_key tokensync pull
Enter fullscreen mode Exit fullscreen mode

On the free Figma plan, use the Styles API instead:

tokensync pull --figma-api styles
Enter fullscreen mode Exit fullscreen mode

Both write a tokens.json ready for tokensync build.

Tokens Studio exports are also supported via a built-in adapter.


How it compares to Style Dictionary

Feature Style Dictionary tokensync
CSS custom properties
Flutter ColorScheme (light + dark)
Flutter TextTheme with named slots
TextStyle with correct letterSpacing
em → px letter-spacing conversion
Named font weights ("SemiBold"FontWeight.w600)
Cross-platform parity check
Figma Variables pull

Getting started

# Install globally
npm install -g tokensync

# Or run with npx
npx tokensync init && npx tokensync build
Enter fullscreen mode Exit fullscreen mode

The init command scaffolds a tokensync.config.ts and a sample tokens.json with colors, spacing, and typography — including light/dark modes. Running build generates all three platform outputs and runs the parity check.

Requirements: Node.js 18+. Zero runtime dependencies. MIT license.

GitHub: [https://github.com/rahulpatwa1303/tokensync]


Current status and what's next

This is v0.1.0. It handles the token types I've encountered in real projects: color, dimension, typography, shadow, number. The generated Dart is idiomatic and passes static analysis.

Planned for v0.2.0: React Native formatter, oklch color support, watch mode improvements.


Do you have this problem?

If you ship both React and Flutter, I'd genuinely like to know how you handle design token sync today. Manual copy-paste? A custom script? A SaaS tool? Something in your CI?

Drop a comment — trying to understand whether this pain is widespread or specific to how my team works.

Top comments (0)