Design Tokens in Angular: Figma Variables to Style Dictionary 2025
Figure 1: The complete design token pipeline from Figma Variables to Angular CSS, SCSS, and TypeScript
Why Design Tokens Transform Your Design System
Without design tokens, your codebase and Figma file drift apart within weeks of launch. A designer changes the primary color; a developer doesn't see the update; the product ships with inconsistent colors. Design tokens solve this by creating a single source of truth that flows automatically from Figma to code.
In 2025, the toolchain is mature: Figma Variables provides the authoring surface, W3C DTCG defines the transport format, Style Dictionary transforms tokens into CSS/SCSS/TypeScript, and Angular consumes the output through standard CSS custom properties. This guide walks you through every step.
Understanding Token Categories
Before building the pipeline, define your token architecture. Most design systems use a three-layer model:
| Layer | Example | Purpose |
|---|---|---|
| Primitive (raw) | color.blue.500 = #3b82f6 |
Raw values; never used directly in components |
| Semantic | color.action.primary = {color.blue.500} |
Intent-based aliases to primitives |
| Component | button.background.default = {color.action.primary} |
Component-specific tokens that alias semantic tokens |
This layering enables dark mode (swap semantic tokens) and rebranding (swap primitive tokens) without touching component code.
Step 1: Define Tokens in W3C DTCG Format
The W3C Design Token Community Group (DTCG) format structures tokens as JSON with $value and $type fields:
{
"color": {
"primitive": {
"blue": {
"500": { "$value": "#3b82f6", "$type": "color" },
"600": { "$value": "#2563eb", "$type": "color" },
"700": { "$value": "#1d4ed8", "$type": "color" }
},
"neutral": {
"0": { "$value": "#ffffff", "$type": "color" },
"900": { "$value": "#111827", "$type": "color" }
}
},
"semantic": {
"action": {
"primary": { "$value": "{color.primitive.blue.600}", "$type": "color" },
"hover": { "$value": "{color.primitive.blue.700}", "$type": "color" }
},
"background": {
"page": { "$value": "{color.primitive.neutral.0}", "$type": "color" },
"subtle": { "$value": "#f9fafb", "$type": "color" }
},
"text": {
"primary": { "$value": "{color.primitive.neutral.900}", "$type": "color" },
"secondary": { "$value": "#6b7280", "$type": "color" }
}
}
},
"spacing": {
"1": { "$value": "4px", "$type": "dimension" },
"2": { "$value": "8px", "$type": "dimension" },
"4": { "$value": "16px", "$type": "dimension" },
"6": { "$value": "24px", "$type": "dimension" },
"8": { "$value": "32px", "$type": "dimension" }
}
}
Step 2: Set Up Figma Variables
Organize your Figma Variables into collections that mirror your token architecture:
- Primitives — one mode, raw values only
- Semantic — two modes: Light and Dark
- Component — references to semantic tokens
Naming Convention
Use consistent naming that maps predictably to CSS custom properties:
| Figma Variable Name | CSS Custom Property | SCSS Variable | TypeScript |
|---|---|---|---|
color/semantic/action/primary |
--color-semantic-action-primary |
$color-semantic-action-primary |
ColorSemanticActionPrimary |
spacing/4 |
--spacing-4 |
$spacing-4 |
Spacing4 |
font-size/body-md |
--font-size-body-md |
$font-size-body-md |
FontSizeBodyMd |
Export Path A: Tokens Studio Plugin
Install the Tokens Studio for Figma plugin, connect to a GitHub repository, and push token JSON automatically on save. The output is already in a Style Dictionary-compatible format.
Export Path B: Figma REST API
For automated CI pipelines, use the Figma API directly:
curl -H "X-Figma-Token: $FIGMA_ACCESS_TOKEN" \
"https://api.figma.com/v1/files/$FIGMA_FILE_ID/variables/local" \
-o figma-variables.json
Transform the raw API response to DTCG format with a Node.js script:
// scripts/export-figma-tokens.mjs
import { writeFileSync } from 'fs';
const FIGMA_TOKEN = process.env.FIGMA_ACCESS_TOKEN;
const FILE_ID = process.env.FIGMA_FILE_ID;
function rgbToHex({ r, g, b }) {
const toHex = (v) => Math.round(v * 255).toString(16).padStart(2, '0');
return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
}
async function exportTokens() {
const res = await fetch(
`https://api.figma.com/v1/files/${FILE_ID}/variables/local`,
{ headers: { 'X-Figma-Token': FIGMA_TOKEN } }
);
const { meta } = await res.json();
const tokens = {};
for (const variable of Object.values(meta.variables)) {
const { name, resolvedType, valuesByMode } = variable;
const path = name.split('/');
let node = tokens;
for (const segment of path.slice(0, -1)) {
node[segment] ??= {};
node = node[segment];
}
const key = path.at(-1);
const rawValue = Object.values(valuesByMode)[0];
node[key] = {
$type: resolvedType === 'COLOR' ? 'color' : 'dimension',
$value: resolvedType === 'COLOR' ? rgbToHex(rawValue) : `${rawValue}px`,
};
}
writeFileSync('tokens/tokens.json', JSON.stringify(tokens, null, 2));
console.log('Tokens exported successfully');
}
exportTokens();
Step 3: Configure Style Dictionary
Style Dictionary transforms your token JSON into platform-specific output. Create sd.config.mjs in your project root:
// sd.config.mjs
import StyleDictionary from 'style-dictionary';
const sd = new StyleDictionary({
source: ['tokens/**/*.json'],
platforms: {
css: {
transformGroup: 'css',
prefix: '',
buildPath: 'src/assets/tokens/',
files: [{
destination: 'tokens.css',
format: 'css/variables',
selector: ':root',
filter: (token) => token.attributes.category !== 'dark',
}],
},
css_dark: {
transformGroup: 'css',
buildPath: 'src/assets/tokens/',
files: [{
destination: 'tokens-dark.css',
format: 'css/variables',
selector: '[data-theme="dark"]',
filter: (token) => token.path.includes('semantic') && token.filePath.includes('dark'),
}],
},
scss: {
transformGroup: 'scss',
buildPath: 'src/styles/',
files: [{
destination: '_tokens.scss',
format: 'scss/variables',
}],
},
ts: {
transformGroup: 'js',
buildPath: 'src/app/tokens/',
files: [{
destination: 'tokens.ts',
format: 'javascript/es6',
}],
},
},
});
await sd.buildAllPlatforms();
Run the build:
node sd.config.mjs
Generated Output
src/assets/tokens/tokens.css:
:root {
--color-primitive-blue-500: #3b82f6;
--color-primitive-blue-600: #2563eb;
--color-semantic-action-primary: #2563eb;
--color-semantic-text-primary: #111827;
--spacing-4: 16px;
}
src/assets/tokens/tokens-dark.css:
[data-theme="dark"] {
--color-semantic-action-primary: #60a5fa;
--color-semantic-background-page: #111827;
--color-semantic-text-primary: #f9fafb;
}
src/styles/_tokens.scss:
$color-semantic-action-primary: #2563eb;
$color-semantic-text-primary: #111827;
$spacing-4: 16px;
src/app/tokens/tokens.ts:
export const ColorSemanticActionPrimary = '#2563eb';
export const ColorSemanticTextPrimary = '#111827';
export const Spacing4 = '16px';
Step 4: Integrate Tokens in Angular
Global Styles
Import both CSS token files in angular.json or styles.scss. Load the same stylesheet in your component workshop too, so the Angular Storybook setup renders components and token swatches with real values:
// src/styles.scss
@import 'assets/tokens/tokens.css';
@import 'assets/tokens/tokens-dark.css';
@import 'styles/tokens'; // SCSS variables
Component Styles
Use CSS custom properties in component stylesheets for automatic dark mode:
// button.component.scss
.ds-button {
background-color: var(--color-semantic-action-primary);
color: var(--color-semantic-text-inverse);
padding: var(--spacing-2) var(--spacing-4);
&:hover {
background-color: var(--color-semantic-action-hover);
}
}
Use SCSS variables when you need compile-time values (e.g., breakpoints):
// layout.component.scss
@use 'styles/tokens' as t;
.ds-layout {
max-width: t.$breakpoint-lg;
padding: t.$spacing-4 t.$spacing-6;
}
TypeScript Constants with Path Aliases
Configure a TypeScript path alias to import token constants cleanly:
// tsconfig.json
{
"compilerOptions": {
"paths": {
"@tokens": ["src/app/tokens/tokens.ts"]
}
}
}
// In any component
import { ColorSemanticActionPrimary } from '@tokens';
// Useful for programmatic theming, chart colors, canvas rendering
const chartColor = ColorSemanticActionPrimary;
Step 5: Implement ThemeService
A ThemeService manages dark/light mode switching by toggling the data-theme attribute:
// src/app/services/theme.service.ts
import { Injectable, signal, effect } from '@angular/core';
import { DOCUMENT } from '@angular/common';
import { inject } from '@angular/core';
export type Theme = 'light' | 'dark';
@Injectable({ providedIn: 'root' })
export class ThemeService {
private document = inject(DOCUMENT);
readonly theme = signal<Theme>(this.getInitialTheme());
constructor() {
effect(() => {
this.applyTheme(this.theme());
});
}
setTheme(theme: Theme): void {
this.theme.set(theme);
localStorage.setItem('theme', theme);
}
toggleTheme(): void {
this.setTheme(this.theme() === 'light' ? 'dark' : 'light');
}
private getInitialTheme(): Theme {
const stored = localStorage.getItem('theme') as Theme | null;
if (stored) return stored;
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
}
private applyTheme(theme: Theme): void {
this.document.documentElement.setAttribute('data-theme', theme);
}
}
Step 6: Automate Token Sync with GitHub Actions
Run token export and Style Dictionary build on a schedule to keep tokens in sync with Figma:
# .github/workflows/sync-tokens.yml
name: Sync Design Tokens from Figma
on:
workflow_dispatch:
repository_dispatch:
types: [figma-tokens-updated]
schedule:
- cron: '0 9 * * 1' # Every Monday at 9:00 AM UTC
jobs:
sync-tokens:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Export tokens from Figma
run: node scripts/export-figma-tokens.mjs
env:
FIGMA_ACCESS_TOKEN: ${{ secrets.FIGMA_ACCESS_TOKEN }}
FIGMA_FILE_ID: ${{ secrets.FIGMA_FILE_ID }}
- name: Build Style Dictionary
run: |
npm ci
node sd.config.mjs
- name: Create Pull Request
uses: peter-evans/create-pull-request@v6
with:
token: ${{ secrets.GITHUB_TOKEN }}
commit-message: 'chore(tokens): sync design tokens from Figma'
branch: 'chore/sync-design-tokens'
title: 'chore(tokens): design token sync from Figma'
body: |
Automated design token sync from Figma Variables.
Review the token changes before merging.
Figma can trigger this via webhook → GitHub repository_dispatch when variables change.
Common Token Pipeline Mistakes
| Mistake | Impact | Fix |
|---|---|---|
| Using primitive tokens directly in components | Breaks dark mode; no semantic meaning | Always use semantic token aliases |
| Hardcoding hex values alongside tokens | Creates drift when tokens update | Enforce via ESLint no-hardcoded-color rule |
| Single-mode tokens for dark/dark support | Dark mode requires full rebuild instead of CSS swap | Author light+dark modes in Figma Variables |
Missing $type in DTCG format |
Style Dictionary can't apply correct transforms | Always declare $type on every token |
| Syncing tokens manually | Tokens drift from Figma within days | Automate with the GitHub Actions workflow |
| No path alias for TypeScript tokens | Brittle relative imports break on refactor | Configure @tokens alias in tsconfig.json |
| Exposing primitive tokens in public API | Consumers bypass semantic layer | Export only semantic and component tokens |
The Result: Zero-Drift Design
With this pipeline in place:
- A designer changes the brand primary color in Figma → Monday morning, a PR appears in GitHub → engineer reviews and merges → components automatically update across the entire Angular app with no manual code changes.
Design tokens aren't just a developer convenience — they're the contract that keeps your design system and your codebase speaking the same language.