Skip to main content

Command Palette

Search for a command to run...

How to Create and Export Design Tokens in Figma for Scalable UI - Pipeline Details

Automate - Extract - Distribute

Updated
8 min read
How to Create and Export Design Tokens in Figma for Scalable UI - Pipeline Details

Building on my previous post, this article provides an in-depth exploration of the token extraction pipeline architecture. I'll also compare this approach with Token Studio, a popular Figma plugin for design token management.

The pipeline is central to the extraction , transformation and bundling of the Figma design tokens. There are two major aspects:

Figma API Integration

The codebase connects to Figma’s API, authenticates, and fetches design tokens directly from the source files. This enables automated, up-to-date extraction without manual exports. All we need is a Figma personal access token and a dedicated FigmaAPI class that handles authentication and communication with Figma's REST endpoints.  Check the documentation on the Figma API’s here

Sample code snippet:

export default class FigmaApi {
  private baseUrl = 'https://api.figma.com'
  private token: string

  constructor(token: string) {
    this.token = token
  }

  async getAllFigmaVariables(fileKey: string) {
    const resp = await axios.request<GetFigmaVariablesResponse>({
      url: `\({this.baseUrl}/v1/files/\){fileKey}/variables/local`,
      headers: {
        Accept: '*/*',
        'X-Figma-Token': this.token,
      },
    })
    return resp.data
  }
}

Key considerations:

  • Authentication: Personal access tokens are stored securely in environment variables

  • File identification: Each Figma file has a unique FILE_KEY that identifies the design system source

  • API versioning: The v1 endpoint ensures stability across Figma's evolving API surface


Token Transformation: From Figma to CSS

The transformation pipeline is the heart of the system, converting Figma's REST API responses into production-ready CSS variables. This multi-stage process handles aliases, multi-mode tokens, color conversions, and special character sanitization. One of the most important and a useful library used here is Style Dictionary. It offers the following benefits

  • Custom Format Extensibility - The custom formatter demonstrates Style Dictionary's most powerful feature—complete control over output format while leveraging its core token infrastructure. It generates mode-specific CSS with alias preservation—impossible with default formatters

  • Focus on output format logic (mode blocks, alias handling)

  • Free multi-platform support (add Android/iOS builds easily)

  • Industry-standard token format (compatible with Design Tokens Community Group spec)

Stage 1 - Figma Variables to JSON Tokens. The first transformation stage converts Figma's variable format into Style Dictionary-compatible JSON after fetching the variables from Figma.

async function main() {
  const fileKey = process.env.FILE_KEY
  const api = new FigmaApi(process.env.PERSONAL_ACCESS_TOKEN)
  
  // Fetch all figma variables from Figma
  const figmaVariables = await api.getAllFigmaVariables(fileKey)
  
  // Transform to token files grouped by collection
  const tokensFiles = tokenFilesFromFigmaVariables(figmaVariables)
  
  // Write JSON files to disk inside the outputDir
  let outputDir = 'tokens_new'
  if (!fs.existsSync(outputDir)) {
    fs.mkdirSync(outputDir)
  }
  // More code
}

Depending on the structure of the JSON, it is not a straight forward conversion to a compatible JSON. Challenges usually encountered include:

  • Special characters in names: Figma allows spaces, dots, and hyphens in variable names. These must be sanitized for file system compatibility and normalized for CSS variable naming (spaces become hyphens, dots become delimiters)

  • Mode handling: Collections with multiple modes (light/dark) require special grouping logic to separate tokens by mode while maintaining a hierarchical structure

  • Alias preservation: References between variables must be captured as aliases (e.g., {Global.white.primary}) rather than resolved immediately, allowing downstream tools to decide when to resolve them

  • Type mapping: Figma's FLOAT type maps to number, BOOLEAN to boolean, etc., ensuring semantic correctness in the output

Custom method like tokenFilesFromFigmaVariables helps mitigating the challenges  and converts Figma's variable format into Style Dictionary-compatible JSON

Sample code snippet:

function tokenFilesFromFigmaVariables(figmaVariables: LocalVariable[]): { [fileName: string]: any } {
  const tokenFiles: { [fileName: string]: any } = {};
  
  figmaVariables.forEach((variable) => {
    const collection = /* find collection for this variable */;
    const hasMultipleModes = collection.modes.length > 1;

    collection.modes.forEach((mode) => {
      // Sanitize collection name for filesystem
      const collectionName = collection.name.replace(/[/\\?%*:|"<>]/g, '-');
      const fileName = `${collectionName}.json`;
      
      // Build hierarchical path
      let path: string[];
      if (hasMultipleModes) {
        // Prefix with mode name for multi-mode collections
        const modeName = mode.name.replace(/\s/g, '');
        path = [modeName, ...variable.name.split('/')];
      } else {
        path = variable.name.split('/');
      }

      // Navigate/create nested structure
      let parent: any = tokenFiles[fileName];
      const tokenName = path.pop()!;

      path.forEach((groupName) => {
        parent[groupName] = parent[groupName] || {};
        parent = parent[groupName];
      });

      // Create token with metadata
      parent[tokenName] = {
    	type: figmaTokenType(variable),
    	value: figmaTokenValue(variable, mode.modeId, figmaVariables),
    	description: variable.description
      }
    });
  });

  return tokenFiles;
}

At the end of Stage 1, we should be able to find the newly extracted token files in the destination that has been specified

Stage 2 - Once the figma design tokens are extracted into the JSON files, we create a config for our StyleDictionary. In this we define the source files, multi-platform support and the  custom formatter that generates mode-aware CSS with intelligent alias handling. The implementation will vary depending on the JSON structure and the kind of output desired. In my case it preserves semantic relationships by emitting original alias notation in mode blocks while resolving global primitives. The formatter generates five distinct sections in the output CSS:

Global Color Primitives (:root**) -** Emits resolved hex values for base color palette tokens that have no mode variation.

:root {

  --global-white-primary: #ffffff;

  --global-red-90: #ff000f;

  --opacity-black-16: #00000029;

}

Mode-Specific Semantic Colors (.mode-light / .mode-dark**) -** Emits original alias notation ({...} braces) to preserve semantic references, later converted to var() by postprocessor. Includes nested elevation classes with resolved box-shadow values.

.mode-light {

  --brand-white: {Global.white.primary};

  --background-base: {Global.white.primary};

}

Spacing & Sizing Tokens (:root**) -** Spacing tokens emit resolved pixel values; MO size tokens emit original aliases to reference spacing scale.

:root {

  --spacing-15: 48px;

  --text-font-size-heading-1: {spacing-15};

}

Typography Utility Classes - Parses composite typography values and generates CSS utility classes with decomposed properties (font-family, size, weight, line-height).

.heading-display-h1 {

  font-family: 'ABCvoice Display';

  font-size: 48px;

  line-height: 64px;

  font-weight: 600;

}

Language Mode Overrides (.language-*) - Groups language-specific font families by locale, enabling i18n font switching via class selectors.

.language-jp {

  --jp-abcvoice: 'ABCvoice JP';

  --jp-abcvoice-display: 'ABCvoice Display';

}

Stage 3 - Postprocessor Normalization. In the final stage, the post processor performs normalisation. It does the following:

  • Alias conversion: {Global.white.primary} -> var(--global-white-primary)

  • Spacing normalization: {spacing-15} -> var(--spacing-15)

  • Brand token mapping: Map --brand-white to var(--global-white-primary)

  • Status deduplication: Remove duplicate declarations

Sample code snippet:

function aliasToVarName(alias) {
  const m = String(alias).trim().match(/^\{([^}]+)\}$/);
  if (!m) return null;
  // Convert {Global.white.primary} → --global-white-primary
  const pathStr = normalizePrefixes(m[1]).replace(/[.]/g, '-').toLowerCase();
  return `--${pathStr}`;
}

function rewriteModeBlockResolveAliases(inner, rootNameToVal, statusRootVars) {
  const declRe = /(\s*--([a-z0-9_-]+)\s*:\s*)([^;]+)(;)/gi;
  
  return inner.replace(declRe, (full, pre, name, val, suf) => {
    const valTrimmed = val.trim();
    
    // Handle brace aliases: {spacing-15} → var(--spacing-15)
    if (/^\{[^}]+\}$/.test(valTrimmed)) {
      const varName = aliasToVarName(valTrimmed);
      if (varName) {
        return `\({pre}var(\){varName})${suf}`;
      }
    }
    
    // Handle brand token mapping: --brand-white → var(--global-white-primary)
    if (name === 'brand-white' && rootNameToVal.has('--global-white-primary')) {
      return `\({pre}var(--global-white-primary)\){suf}`;
    }
    
    // Pass through literal values unchanged
    return full;
  });
}

The final output is a Production-Ready CSS.  Mode tokens reference globals via var(), enabling runtime theme switching. Browsers cache var() resolutions efficiently. It also Aliases document semantic relationships (--bg-primary -> --global-white). The final CSS output demonstrates the multi-stage transformation:

/* Global colors tokens */
:root {
  --global-white-primary: #ffffff;
  --global-black-primary: #000000;
}

/* Semantic color tokens and elevation styles by mode */
.mode-light {
  --brand-white: var(--global-white-primary);
  --background-base: var(--global-white-primary);

.elevation-4 {
    box-shadow: 0px 0px 1px 0px rgba(0, 0, 0, 0.08), 0px 2px 4px 0px rgba(0, 0, 0, 0.08), 0px 8px 16px 0px rgba(0, 0, 0, 0.12);
  }
}

/* Global spacing tokens */
:root {
  --spacing-15: 48px;
}

/* size tokens */
:root {
  --text-font-size-heading-1: var(--spacing-15);
}

/* Language Modes */
.language-jp {
  --jp-abcvoice: 'ABCvoice JP';
  --jp-abcvoice-display: 'ABCvoice Display';
}

All the stages that are mentioned above can be triggered via npm script one after another

npm run sync-figma-to-tokens (Stage1)

npm run build:modes (Stage 2)

npm run postprocess:modes (Stage 3)

Alternative Approach

In lieu of the approach that we discussed, we can also utilise Token Studio plugin (formerly Figma Tokens plugin) that an export design tokens to CSS with variable mappings. It supports:

  • JSON Token Export: Exports tokens in Style Dictionary-compatible JSON format with alias references

    • {
        "color": {
          "global": {
            "white": {
              "primary": { "value": "#ffffff", "type": "color" }
            }
          },
          "background": {
            "base": { 
              "value": "{color.global.white.primary}",
              "type": "color"
            }
          }
        }
      }
      
  • Multi-Mode/Theme Support: Organizes tokens into theme sets (Light, Dark, etc.)

  • Style Dictionary Integration: Token Studio JSON can be processed by Style Dictionary to generate CSS with var() mappings

However we need to weigh in the pros and cons prior to deciding on which one to opt for

Advantages

Limitations

Version Control: JSON tokens live in Git before Figma sync
Designer Self-Service: Designers can manage tokens without code
Cross-Platform: Can work without Figma (edit JSON directly)
Flexibility: More control over token organization

Manual Sync: Requires plugin action to push/pull tokens
Plugin Dependency: Relies on third-party plugin stability
Paid: The essential plan is a paid one
No Native Variables: Doesn't create Figma's native variables (stays in plugin layer)

Conclusion

As a recommendation, the pipeline (Figma Variables -> API -> JSON -> Style Dictionary -> CSS -> Postprocessor) is more robust and maintainable for teams that can manage the technical setup. Token Studio adds designer autonomy but introduces manual sync friction and adds costs