Agent skill
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.
Install this agent skill to your Project
npx add-skill https://github.com/dylantarre/design-system-skills/tree/main/skills/tools/figma
SKILL.md
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 |
Recommended Agent Skills
Expand your agent's capabilities with these related and highly-rated skills.
getting-started
Introduction to the design system skills plugin. Use when first installing the plugin, exploring available skills, or planning a design system implementation.
responsive-typography
Implements fluid typography using CSS clamp() that scales with viewport. Use when creating responsive font sizes, viewport-aware headings, or type that adapts smoothly without breakpoints.
spacing-scale
Generates consistent spacing tokens using base values and ratios. Use when creating margin/padding systems, gap tokens, or layout spacing. Outputs CSS custom properties, Tailwind config, or JSON tokens.
color-scale
Generates perceptually uniform OKLCH color palettes from brand colors. Use when creating color systems, theme palettes, or converting hex values to 11-step scales. Outputs CSS custom properties, Tailwind config, or JSON tokens.
shadow-scale
Generates layered box-shadow elevation tokens for depth hierarchy. Use when creating elevation systems, card shadows, modal depth, or dark mode shadow variants. Outputs CSS, Tailwind, or JSON.
breakpoints
Generates responsive breakpoint tokens for media queries and container queries. Use when setting up responsive design, mobile-first layouts, or viewport-based styling. Outputs CSS, Tailwind, or JSON.
Didn't find tool you were looking for?