DEV Community

Alexander
Alexander

Posted on

Auto-syncing Tailwind config from Figma variables on every PR

A failed Vercel build on a Wednesday afternoon caught my attention. The error log complained about an undefined Tailwind utility class. A designer had renamed a core colour variable in Figma from blue-500 to brand-blue-primary. The design team published the update and pushed the new tokens to our repository. The frontend team pulled the new JSON files. Nobody remembered to update the Tailwind configuration file to match the new token names.

The build crashed because a component was still trying to use bg-blue-500. The token no longer existed in the design system. This happens all the time when you separate your design tokens from your framework configuration. You end up with a manual gap between the JSON file and the actual utility classes your developers use.

I decided to fix this permanently. I wanted a system where updating a Figma variable automatically updates the Tailwind configuration in the same pull request.

The manual mapping trap

Most teams export their Figma variables to a JSON file. They usually dump this file into a tokens folder. Then a developer opens tailwind.config.js and manually maps the JSON values to the Tailwind theme object.

This approach is incredibly fragile. It relies entirely on human memory. When a designer adds a new shade of green, a developer has to remember to wire it up in the config file.

Here is what that manual mapping usually looks like in a typical project.

const tokens = require('./tokens/globals.json')

module.exports = {
  theme: {
    colors: {
      primary: tokens.colors.brand.primary.value,
      secondary: tokens.colors.brand.secondary.value,
      danger: tokens.colors.feedback.danger.value
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

This works fine for three colours. It completely falls apart when you have hundreds of variables across multiple themes. The nested structure of the design tokens rarely matches exactly what Tailwind expects. You also lose the ability to use Tailwind autocomplete properly if the tokens are nested too deeply.

Parsing W3C Design Tokens

Figma variables can be exported in the W3C Design Token format. This is a standard way of representing design decisions in JSON. Every token has a value and a type.

We need a script to read this W3C JSON format and flatten it into something Tailwind understands. Tailwind expects a simple object where keys are the class names and values are the CSS values.

I wrote a Node script to handle this transformation. It recursively reads the token object and builds a flat dictionary.

import fs from 'fs'
import path from 'path'

const tokensPath = path.resolve(process.cwd(), 'tokens/design-tokens.json')
const tokensRaw = fs.readFileSync(tokensPath, 'utf8')
const tokens = JSON.parse(tokensRaw)

function flattenTokens(obj, prefix = '') {
  let result = {}

  for (const key in obj) {
    const value = obj[key]

    if (value && typeof value === 'object' && 'value' in value) {
      const cleanKey = prefix ? `${prefix}-${key}` : key
      result[cleanKey] = value.value
    } else if (value && typeof value === 'object') {
      const newPrefix = prefix ? `${prefix}-${key}` : key
      const nested = flattenTokens(value, newPrefix)
      result = { ...result, ...nested }
    }
  }

  return result
}

const flatColors = flattenTokens(tokens.colors)
Enter fullscreen mode Exit fullscreen mode

This script takes a deeply nested object and turns it into a flat structure. A token located at colors.brand.blue.500 becomes a key called brand-blue-500. This matches the standard Tailwind class naming convention perfectly.

Generating the configuration file

Now we have a flat object of colours. We need to write this object directly into a new Tailwind configuration file.

I prefer generating a TypeScript file rather than a JSON file. This allows us to keep all the standard Tailwind plugins and type definitions intact. We just inject our dynamic colours into the theme extension object.

const configContent = `
import type { Config } from 'tailwindcss'

export const generatedColors = ${JSON.stringify(flatColors, null, 2)}

const config: Config = {
  content: [
    './src/**/*.{js,ts,jsx,tsx,mdx}',
  ],
  theme: {
    extend: {
      colors: generatedColors,
    },
  },
  plugins: [],
}

export default config
`

const outputPath = path.resolve(process.cwd(), 'tailwind.config.ts')
fs.writeFileSync(outputPath, configContent, 'utf8')
console.log('Tailwind config generated successfully.')
Enter fullscreen mode Exit fullscreen mode

When you run this script, it completely overwrites the Tailwind config with the latest tokens. You no longer have to map anything manually. If a designer changes a name in Figma, the script generates a config with the new name.

Automating the sync with GitHub Actions

Running a script locally is better than manual mapping. It is still not fully automated. Developers might forget to run the script after pulling new tokens.

The best place to run this script is in your continuous integration pipeline. We use GitHub Actions to catch token updates and regenerate the Tailwind config automatically.

Whenever a pull request modifies the design tokens JSON file, the workflow triggers. It runs the parser script and commits the updated Tailwind config back to the same pull request.

name: Sync Tailwind Config

on:
  pull_request:
    paths:
      - 'tokens/**.json'

jobs:
  update-config:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          ref: ${{ github.head_ref }}

      - name: Setup Node
        uses: actions/setup-node@v4
        with:
          node-version: '20'

      - name: Install dependencies
        run: npm ci

      - name: Generate Tailwind Config
        run: npm run generate-tailwind

      - name: Commit changes
        run: |
          git config --global user.name 'github-actions[bot]'
          git config --global user.email 'github-actions[bot]@users.noreply.github.com'
          git add tailwind.config.ts
          git commit -m "chore: auto-update tailwind config from design tokens" || echo "No changes to commit"
          git push
Enter fullscreen mode Exit fullscreen mode

This workflow completely removes the human element from the synchronization process. The design tokens act as the absolute source of truth. The framework configuration simply reacts to changes in that source.

Catching drift before it ships

This setup solved our Vercel build failures completely.

Now, when a designer renames a token in Figma, the automated system takes over. The pull request contains the updated JSON file. The GitHub Action runs and updates the tailwind.config.ts file.

During the code review, developers can see exactly how the Tailwind classes will change. If a renamed token breaks an existing component, the standard CI tests will catch the missing class name before the code ever reaches the main branch.

You get a clear visual diff of your design decisions right next to your framework configuration. It forces the design and engineering teams to stay perfectly aligned.

The productised approach

Building the CI pipeline and the parser script handles the code side of things. You still need a reliable way to get the tokens out of Figma and into that pull request in the first place.

I built a tool called Design System Sync to solve this exact problem. It is a Figma plugin that exports your design tokens and variables directly to GitHub or Bitbucket. It automatically generates pull requests with clear visual diffs and assigns reviewers. It exports W3C Design Tokens natively so you can feed them straight into the GitHub Action we just built.

You can learn more about the plugin on the website here https://ds-sync.netlify.app?utm_source=devto&utm_medium=post&utm_campaign=bot and try it out directly from the Figma Community here https://www.figma.com/community/plugin/1561389071519901700?utm_source=devto&utm_medium=post&utm_campaign=bot.

Connecting Figma directly to your Tailwind configuration saves countless hours of debugging. You stop hunting for missing utility classes and start trusting your design system again.

Top comments (0)