Use when syncing design tokens with Figma, setting up Figma Variables, or establishing a Figma-to-code workflow. Covers plugin integration and token formats.
/plugin marketplace add dylantarre/design-system-skills/plugin install design-system-skills@design-system-marketplaceThis skill inherits all available tools. When active, it can use any tool Claude has access to.
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.
| 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 |
| 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 |
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" }
}
}
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;
}
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.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" }
}
}
}
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',
},
],
},
},
};
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 };
}
| 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 |
| 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 |