A bug report rolled in late on a Friday. The primary call to action on our checkout page was completely invisible. Someone had deleted a colour token in Figma. Our automated pipeline updated the JSON file in our repository. The global CSS variables updated perfectly. But a React component deeper in the tree still referenced the old variable name. The browser could not find it. It fell back to transparent. We lost real money because of a simple typo in a token name.
CSS variables are brilliant. They make theming so easy. You can swap a whole colour palette just by changing a class on the body tag. But they have a massive flaw. If you reference a variable that does not exist, the browser just ignores it. There is no build error. There is no warning in the console. You just get a silent failure.
In a large codebase, this is terrifying. Designers rename things. Tokens get deprecated. And you have no way of knowing which components are still relying on those dead tokens. I decided we needed to catch this in CI before we ever merged a pull request.
The naive regex approach
My first thought was to just write a simple script. I wanted to read our exported Figma JSON file and extract all the valid token names. Then I would scan our codebase for any string matching the CSS variable syntax. If the variable was not in the JSON file, the script would throw an error.
Here is the basic JSON structure we get from our design system.
{ "colors": { "brand": { "primary": { "value": "#0d6efd", "type": "color" } } } } This eventually turns into a CSS variable called --colors-brand-primary. So basically we need a script to flatten this JSON and build a list of valid CSS variable names.
Here is the Node script I wrote to do exactly that. Notice I am using standard JavaScript modules.
import fs from 'node:fs' import path from 'node:path' const rawData = fs.readFileSync('./tokens.json', 'utf-8') const tokens = JSON.parse(rawData) const validVariables = new Set() function flattenTokens(obj, prefix = '') { for (const key in obj) { const value = obj[key] if (typeof value === 'object' && value !== null && !value.value) { flattenTokens(value, `${prefix}${key}-`) } else if (value.value) { validVariables.add(`--${prefix}${key}`) } } } flattenTokens(tokens) console.log(validVariables) This works really well for extracting the valid names. The next step is finding where we actually use them in our CSS and React files.
Scanning the codebase for usages
Finding the variables in the codebase is a bit trickier. You could use an Abstract Syntax Tree to parse the CSS. But we also use these variables inline in our React components. So we need something that just looks at raw text.
I wrote a regular expression to find anything that looks like a CSS variable. It looks for var(-- and captures the variable name.
import { globSync } from 'glob' const files = globSync('src/**/*.{css,tsx,jsx}') const variableRegex = /var\((--[a-zA-Z0-9-]+)/g let hasErrors = false for (const file of files) { const content = fs.readFileSync(file, 'utf-8') let match while ((match = variableRegex.exec(content)) !== null) { const variableName = match[1] if (!validVariables.has(variableName)) { console.error(`Error in ${file}: Variable ${variableName} does not exist in tokens`) hasErrors = true } } } if (hasErrors) { process.exit(1) } This script is small but it is incredibly powerful. It literally reads every source file and checks every single CSS variable usage against our source of truth. If a designer deletes a token in Figma, this script will fail the build if we are still using it.
The problem with dynamic variables
We ran this script on our main repository. It immediately found twelve dead variables. That felt like a massive win. But then we hit a wall.
Sometimes developers construct CSS variable names dynamically in JavaScript. They might do something like this in a React component.
const buttonColor = `var(--brand-${variant}-500)` return ( <button style={{ backgroundColor: buttonColor }}> Click me </button> ) Our regex script completely chokes on this. It tries to look up --brand-${variant}-500 in our token list. Obviously that does not exist. It is a dynamic string.
We had two choices. We could try to make the regex smarter. Or we could just ban dynamic CSS variable names entirely.
Honestly, we chose the second option. Banning dynamic variable names makes your codebase much easier to search. If you need a dynamic colour, you should map it to a static variable using a data attribute.
button[data-variant="primary"] { background-color: var(--brand-primary-500); } button[data-variant="secondary"] { background-color: var(--brand-secondary-500); } This is much cleaner. And it means our simple regex script works perfectly.
Wiring it up in GitHub Actions
The final piece of the puzzle is running this automatically. You do not want developers to remember to run this script locally. It needs to happen on every pull request.
Here is the GitHub Action workflow we use. It is very straightforward. It checks out the code, installs dependencies, and runs our validation script.
name: Validate Design Tokens on: pull_request: branches: - main jobs: validate-tokens: runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v4 - name: Setup Node uses: actions/setup-node@v4 with: node-version: 20 - name: Install dependencies run: npm ci - name: Run token validator run: node scripts/validate-tokens.js Now, if someone tries to merge a PR that uses a dead CSS variable, the build goes red. The reviewer can see exactly which file contains the error. It completely eliminates the risk of silent CSS failures in production. this is the one thing nobody talks about when setting up design systems. You need strict validation just like you have for TypeScript.
Getting the tokens into the repo
Writing the validation script is the easy part tbh. The hardest part of this entire workflow is actually getting the token JSON from Figma into your repository reliably.
For a long time, we just asked designers to manually export a JSON file and send it to us on Slack. It was a terrible process. Files got lost. People forgot to send updates. We ended up with massive drift between Figma and code.
I got so tired of this that I built a tool to fix it. I am the creator of Design System Sync. It is a Figma plugin that exports your design tokens directly to GitHub or Bitbucket via automatic pull requests.
You just map your Figma variables, click export, and it generates a PR with the updated JSON and CSS files. It even assigns reviewers automatically. It supports W3C Design Tokens, standard CSS Variables, and Style Dictionary formats. It handles multi-mode variables for light and dark themes perfectly.
You can check out the website here: https://ds-sync.netlify.app?utm_source=devto&utm_medium=post&utm_campaign=bot
And you can grab the plugin directly from the Figma Community here: https://www.figma.com/community/plugin/1561389071519901700?utm_source=devto&utm_medium=post&utm_campaign=bot
If you combine an automated export tool with a strict CI validation script, your design system becomes bulletproof. Designers can clean up tokens with confidence. Developers never have to worry about broken UI. It is just a much better way to work.