agentsclimarketplace

Figma

Skill dylantarre/design-system-skills/skills/tools/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

Install
npx -y skills add dylantarre/design-system-skills --skill figma

Assembled 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 TypeMaps ToExample
Color Variable--color-*--color-primary-500
Number Variable--spacing-*, --radius-*--spacing-md
String VariableFont family, mode names--font-sans
Boolean VariableFeature flagsTheme mode toggles

The Process

  1. Choose workflow direction:
    • Code → Figma (tokens are source of truth in code)
    • Figma → Code (designers own tokens in Figma)
    • Bidirectional (sync both ways)
  2. Select tooling: Native Variables, Tokens Studio, or custom
  3. Define token structure: Categories, naming, modes
  4. Set up sync mechanism: Plugin, API, or manual
  5. Document workflow: Who updates what, when to sync

Workflow Options

ApproachBest ForTools
Figma Variables (native)Simple setups, color/number tokensFigma UI, REST API
Tokens StudioComplex tokens, multi-fileTokens Studio plugin + GitHub
Style DictionaryEnterprise, CI/CD integrationStyle Dictionary + Figma API
Manual syncSmall teams, infrequent changesCopy/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

CategoryFigma ConventionCode Convention
Colorscolor/primary/500--color-primary-500
Spacingspacing/md--spacing-md
Radiusradius/lg--radius-lg
Typographytext/body/regular--text-body
Shadowsshadow/md--shadow-md
Semanticbg/default--color-bg-default

Workflow Best Practices

  1. Single source of truth: Decide if code or Figma owns tokens
  2. Version control: Store token files in Git
  3. CI/CD integration: Auto-sync on merge to main
  4. Semantic tokens: Use aliases for theme-aware values
  5. Documentation: Keep a mapping document for designers
  6. Validation: Lint tokens before sync

Common Pitfalls

IssueSolution
Color format mismatchNormalize to hex or RGB before sync
Spacing unitsUse unitless in Figma, add px/rem in code
Missing tokensValidate token coverage before sync
Mode driftSync all modes together, not separately
Name collisionsEstablish naming convention upfront

Keep looking

Skills are one crate of 627,801. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.