Figma
Syncs design tokens between code and Figma using Variables API or Tokens Studio plugin. Use when establishing Figma-to-code workflows, exporting Figma tokens, or setting up design-development handoff.From its SKILL.md
npx -y skills add dylantarre/design-system-skills --skill figmaAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
SKILL.md
13.5 KB, as published. Nobody here has run it
Figma Token Integration
Overview
Sync design tokens between code and Figma using Figma Variables, the Tokens Studio plugin, or Style Dictionary. Establish a single source of truth workflow for design-to-code handoff.
When to Use
- Setting up Figma Variables from code tokens
- Exporting Figma styles to code tokens
- Establishing a design token workflow
- Syncing colors, typography, and spacing with Figma
Quick Reference: Figma Token Types
| Figma Type | Maps To | Example |
|---|---|---|
| Color Variable | --color-* | --color-primary-500 |
| Number Variable | --spacing-*, --radius-* | --spacing-md |
| String Variable | Font family, mode names | --font-sans |
| Boolean Variable | Feature flags | Theme mode toggles |
The Process
- Choose workflow direction:
- Code → Figma (tokens are source of truth in code)
- Figma → Code (designers own tokens in Figma)
- Bidirectional (sync both ways)
- Select tooling: Native Variables, Tokens Studio, or custom
- Define token structure: Categories, naming, modes
- Set up sync mechanism: Plugin, API, or manual
- Document workflow: Who updates what, when to sync
Workflow Options
| Approach | Best For | Tools |
|---|---|---|
| Figma Variables (native) | Simple setups, color/number tokens | Figma UI, REST API |
| Tokens Studio | Complex tokens, multi-file | Tokens Studio plugin + GitHub |
| Style Dictionary | Enterprise, CI/CD integration | Style Dictionary + Figma API |
| Manual sync | Small teams, infrequent changes | Copy/paste with conventions |
Figma Variables (Native)
Creating Variables from Code Tokens
Token structure for Figma:
{
"color": {
"primary": {
"50": { "value": "#eff6ff", "type": "color" },
"100": { "value": "#dbeafe", "type": "color" },
"500": { "value": "#3b82f6", "type": "color" },
"900": { "value": "#1e3a8a", "type": "color" }
},
"gray": {
"50": { "value": "#f9fafb", "type": "color" },
"900": { "value": "#111827", "type": "color" }
}
},
"spacing": {
"xs": { "value": 4, "type": "number" },
"sm": { "value": 8, "type": "number" },
"md": { "value": 16, "type": "number" },
"lg": { "value": 24, "type": "number" }
},
"radius": {
"sm": { "value": 4, "type": "number" },
"md": { "value": 8, "type": "number" },
"lg": { "value": 16, "type": "number" },
"full": { "value": 9999, "type": "number" }
}
}
Figma REST API Example
Create variables via API:
import Anthropic from '@anthropic-ai/sdk'; // Example using Figma API
interface FigmaVariable {
name: string;
resolvedType: 'COLOR' | 'FLOAT' | 'STRING' | 'BOOLEAN';
valuesByMode: Record<string, any>;
}
async function createFigmaVariables(
fileKey: string,
collectionName: string,
tokens: Record<string, any>
): Promise<void> {
const accessToken = process.env.FIGMA_ACCESS_TOKEN;
// Step 1: Create variable collection
const collectionResponse = await fetch(
`https://api.figma.com/v1/files/${fileKey}/variables`,
{
method: 'POST',
headers: {
'X-Figma-Token': accessToken,
'Content-Type': 'application/json',
},
body: JSON.stringify({
variableCollections: [
{
action: 'CREATE',
name: collectionName,
initialModeId: 'light',
},
],
}),
}
);
const { variableCollections } = await collectionResponse.json();
const collectionId = variableCollections[0].id;
// Step 2: Create variables
const variables: FigmaVariable[] = [];
for (const [category, values] of Object.entries(tokens)) {
for (const [name, token] of Object.entries(values as Record<string, any>)) {
variables.push({
name: `${category}/${name}`,
resolvedType: token.type === 'color' ? 'COLOR' : 'FLOAT',
valuesByMode: {
light: parseValue(token.value, token.type),
},
});
}
}
await fetch(`https://api.figma.com/v1/files/${fileKey}/variables`, {
method: 'POST',
headers: {
'X-Figma-Token': accessToken,
'Content-Type': 'application/json',
},
body: JSON.stringify({
variables: variables.map((v) => ({
action: 'CREATE',
variableCollectionId: collectionId,
...v,
})),
}),
});
}
function parseValue(value: string | number, type: string): any {
if (type === 'color') {
// Convert hex to Figma RGBA
const hex = value as string;
const r = parseInt(hex.slice(1, 3), 16) / 255;
const g = parseInt(hex.slice(3, 5), 16) / 255;
const b = parseInt(hex.slice(5, 7), 16) / 255;
return { r, g, b, a: 1 };
}
return value;
}
Variable Modes (Theming)
Light/dark mode setup:
{
"variableCollections": [
{
"name": "Semantic Colors",
"modes": [
{ "name": "Light", "modeId": "light" },
{ "name": "Dark", "modeId": "dark" }
]
}
],
"variables": [
{
"name": "background/default",
"valuesByMode": {
"light": { "type": "VARIABLE_ALIAS", "id": "color/gray/50" },
"dark": { "type": "VARIABLE_ALIAS", "id": "color/gray/900" }
}
},
{
"name": "text/primary",
"valuesByMode": {
"light": { "type": "VARIABLE_ALIAS", "id": "color/gray/900" },
"dark": { "type": "VARIABLE_ALIAS", "id": "color/gray/50" }
}
}
]
}
Tokens Studio Integration
Token File Structure
tokens.json (Tokens Studio format):
{
"global": {
"colors": {
"primary": {
"50": { "value": "#eff6ff", "type": "color" },
"500": { "value": "#3b82f6", "type": "color" },
"900": { "value": "#1e3a8a", "type": "color" }
},
"gray": {
"50": { "value": "#f9fafb", "type": "color" },
"900": { "value": "#111827", "type": "color" }
}
},
"spacing": {
"xs": { "value": "4", "type": "spacing" },
"sm": { "value": "8", "type": "spacing" },
"md": { "value": "16", "type": "spacing" },
"lg": { "value": "24", "type": "spacing" }
},
"borderRadius": {
"sm": { "value": "4", "type": "borderRadius" },
"md": { "value": "8", "type": "borderRadius" },
"lg": { "value": "16", "type": "borderRadius" }
},
"fontFamilies": {
"sans": { "value": "Inter", "type": "fontFamilies" },
"mono": { "value": "JetBrains Mono", "type": "fontFamilies" }
},
"fontSizes": {
"xs": { "value": "12", "type": "fontSizes" },
"sm": { "value": "14", "type": "fontSizes" },
"base": { "value": "16", "type": "fontSizes" },
"lg": { "value": "18", "type": "fontSizes" }
},
"lineHeights": {
"tight": { "value": "1.25", "type": "lineHeights" },
"normal": { "value": "1.5", "type": "lineHeights" },
"relaxed": { "value": "1.75", "type": "lineHeights" }
}
},
"light": {
"bg": {
"default": { "value": "{global.colors.gray.50}", "type": "color" },
"subtle": { "value": "{global.colors.gray.100}", "type": "color" }
},
"text": {
"primary": { "value": "{global.colors.gray.900}", "type": "color" },
"secondary": { "value": "{global.colors.gray.600}", "type": "color" }
}
},
"dark": {
"bg": {
"default": { "value": "{global.colors.gray.900}", "type": "color" },
"subtle": { "value": "{global.colors.gray.800}", "type": "color" }
},
"text": {
"primary": { "value": "{global.colors.gray.50}", "type": "color" },
"secondary": { "value": "{global.colors.gray.400}", "type": "color" }
}
}
}
Tokens Studio → Code Transform
Style Dictionary config (sd.config.js):
module.exports = {
source: ['tokens/**/*.json'],
platforms: {
css: {
transformGroup: 'css',
buildPath: 'dist/css/',
files: [
{
destination: 'variables.css',
format: 'css/variables',
filter: (token) => token.filePath.includes('global'),
},
{
destination: 'light.css',
format: 'css/variables',
filter: (token) => token.filePath.includes('light'),
options: {
selector: ':root, [data-theme="light"]',
},
},
{
destination: 'dark.css',
format: 'css/variables',
filter: (token) => token.filePath.includes('dark'),
options: {
selector: '[data-theme="dark"]',
},
},
],
},
tailwind: {
transformGroup: 'js',
buildPath: 'dist/',
files: [
{
destination: 'tailwind.tokens.js',
format: 'javascript/module',
},
],
},
},
};
Figma Plugin Development
Plugin for Token Sync
manifest.json:
{
"name": "Token Sync",
"id": "token-sync-plugin",
"api": "1.0.0",
"main": "code.js",
"ui": "ui.html",
"editorType": ["figma"],
"networkAccess": {
"allowedDomains": ["api.github.com"]
}
}
code.ts (plugin logic):
figma.showUI(__html__, { width: 400, height: 500 });
figma.ui.onmessage = async (msg) => {
if (msg.type === 'import-tokens') {
await importTokens(msg.tokens);
} else if (msg.type === 'export-tokens') {
const tokens = await exportTokens();
figma.ui.postMessage({ type: 'tokens-exported', tokens });
}
};
async function importTokens(tokens: Record<string, any>): Promise<void> {
// Get or create collection
const collections = await figma.variables.getLocalVariableCollectionsAsync();
let collection = collections.find((c) => c.name === 'Design Tokens');
if (!collection) {
collection = figma.variables.createVariableCollection('Design Tokens');
}
const modeId = collection.modes[0].modeId;
// Import each token
for (const [path, token] of flattenTokens(tokens)) {
const existing = await figma.variables.getVariableByIdAsync(
`VariableID:${path.replace(/\//g, ':')}`
);
if (existing) {
existing.setValueForMode(modeId, parseTokenValue(token));
} else {
const variable = figma.variables.createVariable(
path,
collection,
getVariableType(token.type)
);
variable.setValueForMode(modeId, parseTokenValue(token));
}
}
figma.notify(`Imported ${Object.keys(tokens).length} tokens`);
}
async function exportTokens(): Promise<Record<string, any>> {
const tokens: Record<string, any> = {};
const collections = await figma.variables.getLocalVariableCollectionsAsync();
for (const collection of collections) {
for (const variableId of collection.variableIds) {
const variable = await figma.variables.getVariableByIdAsync(variableId);
if (!variable) continue;
const path = variable.name.split('/');
let current = tokens;
for (let i = 0; i < path.length - 1; i++) {
current[path[i]] = current[path[i]] || {};
current = current[path[i]];
}
const modeId = collection.modes[0].modeId;
current[path[path.length - 1]] = {
value: formatValue(variable.valuesByMode[modeId], variable.resolvedType),
type: mapVariableType(variable.resolvedType),
};
}
}
return tokens;
}
function* flattenTokens(
obj: Record<string, any>,
path: string[] = []
): Generator<[string, any]> {
for (const [key, value] of Object.entries(obj)) {
if (value.value !== undefined) {
yield [path.concat(key).join('/'), value];
} else {
yield* flattenTokens(value, path.concat(key));
}
}
}
function getVariableType(type: string): VariableResolvedDataType {
switch (type) {
case 'color':
return 'COLOR';
case 'spacing':
case 'borderRadius':
case 'fontSizes':
return 'FLOAT';
default:
return 'STRING';
}
}
function parseTokenValue(token: { value: string; type: string }): VariableValue {
if (token.type === 'color') {
return hexToRgba(token.value);
}
if (['spacing', 'borderRadius', 'fontSizes'].includes(token.type)) {
return parseFloat(token.value);
}
return token.value;
}
function hexToRgba(hex: string): RGBA {
const r = parseInt(hex.slice(1, 3), 16) / 255;
const g = parseInt(hex.slice(3, 5), 16) / 255;
const b = parseInt(hex.slice(5, 7), 16) / 255;
return { r, g, b, a: 1 };
}
Naming Conventions
| Category | Figma Convention | Code Convention |
|---|---|---|
| Colors | color/primary/500 | --color-primary-500 |
| Spacing | spacing/md | --spacing-md |
| Radius | radius/lg | --radius-lg |
| Typography | text/body/regular | --text-body |
| Shadows | shadow/md | --shadow-md |
| Semantic | bg/default | --color-bg-default |
Workflow Best Practices
- Single source of truth: Decide if code or Figma owns tokens
- Version control: Store token files in Git
- CI/CD integration: Auto-sync on merge to main
- Semantic tokens: Use aliases for theme-aware values
- Documentation: Keep a mapping document for designers
- Validation: Lint tokens before sync
Common Pitfalls
| Issue | Solution |
|---|---|
| Color format mismatch | Normalize to hex or RGB before sync |
| Spacing units | Use unitless in Figma, add px/rem in code |
| Missing tokens | Validate token coverage before sync |
| Mode drift | Sync all modes together, not separately |
| Name collisions | Establish naming convention upfront |