DEV Community

Alexander
Alexander

Posted on

Building a Figma to GitHub token pipeline that actually works

The problem

Picture this. It is a Friday afternoon. Your team is getting ready for a big release. The design team decided earlier that week to tweak the primary brand colour. They changed it from a flat blue to a slightly more vibrant indigo. They updated Figma. They dropped a message in a crowded Slack channel.

And you completely missed it.

The release goes out with the old blue. The designers are frustrated. The developers are annoyed because they were not told properly. Honestly this happens all the time. Hardcoding colours and spacing values into CSS is a recipe for disaster.

We realised we needed a single source of truth. Figma had to be that source. But we also needed developers to review changes before they hit production. We needed a pipeline that turned Figma variables into actual code. And we needed it to happen via standard Pull Requests.

What we tried that didn't work

Our first attempt was pretty basic. We asked the designers to export a JSON file from Figma manually. Then they would zip it and send it to us on Slack. It was a disaster. People forgot to send the file. Versions got mixed up. We had tokens_final_v3_really_final.json floating around.

Next we tried writing a quick local script. A developer would run a command on their machine to fetch the tokens via the Figma REST API. This was slightly better. But it meant developers had to remember to pull the updates. The design team had no control over when their changes went live.

We needed a system where the design team could push changes directly to the codebase. But they had to do it safely. They needed to create a Pull Request automatically.

The architecture

We mapped out a new flow. It had to be simple for both sides.

First the designers update their variables in Figma. When they are happy they run a process to export those variables as standard W3C Design Tokens.

This process talks directly to the GitHub API. It creates a new branch. It commits the JSON file. Then it opens a Pull Request.

Finally a GitHub Action picks up that Pull Request. It runs Style Dictionary to transform the raw JSON into CSS variables. Developers just review the PR and click merge. Everyone stays in their comfort zone.

Implementation walkthrough

Let me show you exactly how we built the core parts of this pipeline.

First you need the tokens in a standard format. The W3C Design Tokens format is the best way to go. It looks something like this.

{
  "colors": {
    "primary": {
      "500": {
        "$value": "#4F46E5",
        "$type": "color"
      }
    },
    "background": {
      "$value": "{colors.primary.500}",
      "$type": "color"
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

The magic happens when you send this to GitHub. You can write a Node script to interact with the GitHub API. You need a personal access token with repo permissions.

Here is a simplified version of the script that creates a new file in a branch.

import { Octokit } from "@octokit/rest";

const octokit = new Octokit({ auth: process.env.GITHUB_TOKEN });

async function pushTokensToGitHub(tokensJson: string) {
  const owner = "your-org";
  const repo = "your-repo";
  const branchName = `design-tokens-update-${Date.now()}`;

  // Get the main branch reference
  const { data: refData } = await octokit.git.getRef({
    owner,
    repo,
    ref: "heads/main",
  });

  // Create a new branch
  await octokit.git.createRef({
    owner,
    repo,
    ref: `refs/heads/${branchName}`,
    sha: refData.object.sha,
  });

  // Commit the file
  await octokit.repos.createOrUpdateFileContents({
    owner,
    repo,
    path: "tokens/design-tokens.json",
    message: "Update design tokens from Figma",
    content: Buffer.from(tokensJson).toString("base64"),
    branch: branchName,
  });

  // Open a Pull Request
  await octokit.pulls.create({
    owner,
    repo,
    title: "🎨 Update Design Tokens",
    head: branchName,
    base: "main",
    body: "This PR was automatically generated to sync the latest design tokens.",
  });
}
Enter fullscreen mode Exit fullscreen mode

Once the PR is created you need to turn that JSON into CSS. We use a tool called Style Dictionary for this. You set up a configuration file to tell it how to process the W3C format.

// build.js
const StyleDictionary = require('style-dictionary');

const myStyleDictionary = StyleDictionary.extend({
  source: ['tokens/**/*.json'],
  platforms: {
    css: {
      transformGroup: 'css',
      buildPath: 'build/css/',
      files: [{
        destination: 'variables.css',
        format: 'css/variables'
      }]
    }
  }
});

myStyleDictionary.buildAllPlatforms();
Enter fullscreen mode Exit fullscreen mode

To automate this we added a GitHub Actions workflow. Every time a PR is opened or updated the action runs Style Dictionary and adds the compiled CSS to the PR.

name: Compile Design Tokens

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

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
        with:
          ref: ${{ github.event.pull_request.head.ref }}

      - name: Setup Node
        uses: actions/setup-node@v3
        with:
          node-version: '18'

      - name: Install dependencies
        run: npm install

      - name: Build tokens
        run: node build.js

      - name: Commit compiled CSS
        run: |
          git config --global user.name 'github-actions[bot]'
          git config --global user.email 'github-actions[bot]@users.noreply.github.com'
          git add build/css/variables.css
          git commit -m "chore: compile tokens to CSS" || echo "No changes to commit"
          git push
Enter fullscreen mode Exit fullscreen mode

Edge cases we hit

Building this wasn't totally smooth sailing. We ran into a few annoying edge cases.

The biggest issue was naming conventions. Designers love spaces and capital letters in their Figma variable names. Developers hate that. We had to write a custom transformer in Style Dictionary to clean up the names. It forces everything into kebab case. This prevents CSS errors.

Another tricky part was dealing with light and dark modes. Figma handles modes beautifully now. But exporting them requires you to split the modes into separate JSON files or nest them carefully. We ended up exporting a light.json and a dark.json. Then we configured Style Dictionary to output them into separate CSS files wrapped in media queries.

Math was also a problem. Sometimes designers use calculations for spacing variables. Standard JSON does not understand math formulas. We had to add a parser step to calculate those values before saving the JSON.

Results

Getting this pipeline running changed everything for us.

The friction between design and development dropped to zero. Designers now have total control over the visual foundation. When they want to update a colour they just hit export. A Pull Request pops up. The developers look at the diff to ensure nothing looks wildly broken. Then they merge it.

No more missing Slack messages. No more hunting down random hex codes in the codebase.

I actually spent the last few months turning this exact workflow into a proper Figma plugin. Setting up the GitHub API scripts and token transformers from scratch is a bit tedious. I wanted a tool that just did it out of the box.

I call it Design System Sync. It connects your Figma files directly to GitHub or Bitbucket. It exports your Figma variables into W3C Design Tokens or CSS Variables automatically. It even generates the Pull Requests with visual diffs so developers know exactly what changed.

You can check out the website at https://ds-sync.netlify.app?utm_source=devto&utm_medium=post&utm_campaign=bot. You can also grab it directly from the Figma Community if you want to give it a spin. It has a free tier that covers most small projects perfectly.

Building a solid bridge between design and code takes a bit of effort upfront. But the peace of mind is totally worth it. Let me know if you have tried building something similar for your team.

Top comments (0)