Extract components from Figma, convert designs to React components, sync design tokens, and generate code from designs. Bridge the gap between design and code with automated workflows.
Turn Figma designs into production-ready code.
Design is the single source of truth.
Designers work in Figma. Developers build from Figma. The bridge between them should be automated, not manual.
# .env
FIGMA_ACCESS_TOKEN=figd_...
npm install node-fetch
import { FigmaClient } from '@/integrations/design-tools/figma/client'
const client = new FigmaClient({
accessToken: process.env.FIGMA_ACCESS_TOKEN
})
// Test with a public file
const file = await client.getFile('abc123xyz')
console.log('Connected! File:', file.name)
Design tokens are design decisions (colors, typography, spacing) stored as code.
Benefits:
// scripts/sync-design-tokens.ts
import { FigmaClient } from '@/integrations/design-tools/figma/client'
import fs from 'fs/promises'
async function syncDesignTokens() {
const client = new FigmaClient()
const fileKey = 'YOUR_FIGMA_FILE_KEY'
console.log('Extracting design tokens...')
// Extract tokens
const tokens = await client.extractDesignTokens(fileKey)
console.log(`Found:`)
console.log(` ${tokens.colors.length} colors`)
console.log(` ${tokens.typography.length} text styles`)
console.log(` ${tokens.spacing.length} spacing values`)
// Export as CSS
const css = await client.exportTokensAsCSS(fileKey)
await fs.writeFile('src/styles/design-tokens.css', css)
// Export as JSON
const json = await client.exportTokensAsJSON(fileKey)
await fs.writeFile('src/styles/design-tokens.json', json)
console.log('Design tokens synced!')
}
syncDesignTokens()
// src/styles/design-tokens.css
:root {
/* Colors */
--color-primary: #0066cc;
--color-secondary: #10b981;
--color-neutral-100: #f9fafb;
--color-neutral-900: #111827;
/* Typography */
--font-heading-family: Inter;
--font-heading-size: 48px;
--font-heading-weight: 700;
/* Spacing */
--space-4: 16px;
--space-8: 32px;
}
Usage in React:
// components/Button.tsx
export function Button({ children }: { children: React.ReactNode }) {
return (
<button
style={{
backgroundColor: 'var(--color-primary)',
color: 'white',
padding: 'var(--space-4)',
fontFamily: 'var(--font-heading-family)',
fontWeight: 'var(--font-heading-weight)',
border: 'none',
borderRadius: '8px',
cursor: 'pointer'
}}
>
{children}
</button>
)
}
// scripts/export-icons.ts
import { FigmaClient } from '@/integrations/design-tools/figma/client'
import fs from 'fs/promises'
async function exportIcons() {
const client = new FigmaClient()
const fileKey = 'YOUR_FIGMA_FILE_KEY'
// Get file
const file = await client.getFile(fileKey)
// Find "Icons" frame
const iconsFrame = findNode(file.document, 'Icons')
if (!iconsFrame || !iconsFrame.children) {
throw new Error('Icons frame not found')
}
console.log(`Found ${iconsFrame.children.length} icons`)
// Export as SVG
const iconIds = iconsFrame.children.map(child => child.id)
const svgs = await client.exportImages(fileKey, iconIds, {
format: 'svg'
})
// Save each SVG
for (const svg of svgs) {
const response = await fetch(svg.url)
const content = await response.text()
await fs.writeFile(`public/icons/${svg.name}.svg`, content)
console.log(` ✓ ${svg.name}.svg`)
}
console.log('Icons exported!')
}
function findNode(node: any, name: string): any {
if (node.name === name) return node
if (node.children) {
for (const child of node.children) {
const found = findNode(child, name)
if (found) return found
}
}
return null
}
exportIcons()
// scripts/generate-icon-components.ts
import { FigmaClient } from '@/integrations/design-tools/figma/client'
import fs from 'fs/promises'
async function generateIconComponents() {
const client = new FigmaClient()
const fileKey = 'YOUR_FIGMA_FILE_KEY'
const file = await client.getFile(fileKey)
const iconsFrame = findNode(file.document, 'Icons')
if (!iconsFrame || !iconsFrame.children) {
throw new Error('Icons frame not found')
}
// Export icons
const iconIds = iconsFrame.children.map(child => child.id)
const svgs = await client.exportImages(fileKey, iconIds, {
format: 'svg'
})
// Generate React components
for (const svg of svgs) {
const response = await fetch(svg.url)
const svgContent = await response.text()
// Convert to React component
const componentName = toPascalCase(svg.name)
const component = `
import React from 'react'
export function ${componentName}Icon(props: React.SVGProps<SVGSVGElement>) {
return (
${svgContent.replace('<svg', '<svg {...props}')}
)
}
`.trim()
await fs.writeFile(`components/icons/${componentName}Icon.tsx`, component)
console.log(` ✓ ${componentName}Icon.tsx`)
}
// Generate index file
const indexContent = svgs
.map(svg => {
const componentName = toPascalCase(svg.name)
return `export { ${componentName}Icon } from './${componentName}Icon'`
})
.join('\n')
await fs.writeFile('components/icons/index.ts', indexContent)
console.log('Icon components generated!')
}
function toPascalCase(str: string): string {
return str
.split(/[-_\s]+/)
.map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
.join('')
}
function findNode(node: any, name: string): any {
if (node.name === name) return node
if (node.children) {
for (const child of node.children) {
const found = findNode(child, name)
if (found) return found
}
}
return null
}
generateIconComponents()
Usage:
import { HomeIcon, UserIcon, SettingsIcon } from '@/components/icons'
export function Navigation() {
return (
<nav>
<HomeIcon width={24} height={24} />
<UserIcon width={24} height={24} />
<SettingsIcon width={24} height={24} />
</nav>
)
}
// scripts/extract-components.ts
import { FigmaClient } from '@/integrations/design-tools/figma/client'
async function extractComponents() {
const client = new FigmaClient()
const fileKey = 'YOUR_FIGMA_FILE_KEY'
// Get components
const components = await client.getFileComponents(fileKey)
console.log('Components:')
for (const [key, component] of Object.entries(components)) {
console.log(` ${component.name}`)
console.log(` Key: ${component.key}`)
console.log(` Description: ${component.description}`)
}
// Get component sets (variants)
const componentSets = await client.getComponentSets(fileKey)
console.log('\nComponent Sets:')
for (const [setId, variants] of Object.entries(componentSets)) {
console.log(` Set: ${setId}`)
for (const variant of variants) {
console.log(` - ${variant.name}`)
}
}
}
extractComponents()
// scripts/generate-button.ts
import { FigmaClient } from '@/integrations/design-tools/figma/client'
import fs from 'fs/promises'
async function generateButtonComponent() {
const client = new FigmaClient()
const fileKey = 'YOUR_FIGMA_FILE_KEY'
// Get button component
const components = await client.getFileComponents(fileKey)
const buttonComponent = Object.values(components).find(c =>
c.name.toLowerCase().includes('button')
)
if (!buttonComponent) {
throw new Error('Button component not found')
}
// Get component node
const file = await client.getFile(fileKey)
const buttonNode = findNodeById(file.document, buttonComponent.key)
if (!buttonNode) {
throw new Error('Button node not found')
}
// Extract styles
const styles = extractStyles(buttonNode)
// Generate React component
const component = `
import React from 'react'
interface ButtonProps {
variant?: 'primary' | 'secondary' | 'ghost'
size?: 'sm' | 'md' | 'lg'
children: React.ReactNode
onClick?: () => void
}
export function Button({
variant = 'primary',
size = 'md',
children,
onClick
}: ButtonProps) {
const baseStyles = {
fontFamily: '${styles.fontFamily}',
fontWeight: ${styles.fontWeight},
fontSize: '${styles.fontSize}px',
padding: '${styles.padding}',
borderRadius: '${styles.borderRadius}px',
border: 'none',
cursor: 'pointer',
transition: 'all 0.2s'
}
const variantStyles = {
primary: {
backgroundColor: '${styles.backgroundColor}',
color: '${styles.color}'
},
secondary: {
backgroundColor: 'transparent',
color: '${styles.backgroundColor}',
border: '2px solid ${styles.backgroundColor}'
},
ghost: {
backgroundColor: 'transparent',
color: '${styles.color}'
}
}
return (
<button
style={{ ...baseStyles, ...variantStyles[variant] }}
onClick={onClick}
>
{children}
</button>
)
}
`.trim()
await fs.writeFile('components/Button.tsx', component)
console.log('Button component generated!')
}
function findNodeById(node: any, id: string): any {
if (node.id === id) return node
if (node.children) {
for (const child of node.children) {
const found = findNodeById(child, id)
if (found) return found
}
}
return null
}
function extractStyles(node: any) {
return {
fontFamily: node.style?.fontFamily || 'Inter',
fontWeight: node.style?.fontWeight || 600,
fontSize: node.style?.fontSize || 16,
padding: '12px 24px',
borderRadius: node.cornerRadius || 8,
backgroundColor: rgbToHex(node.fills?.[0]?.color || { r: 0, g: 0.4, b: 0.8 }),
color: '#ffffff'
}
}
function rgbToHex(color: any): string {
const r = Math.round(color.r * 255)
.toString(16)
.padStart(2, '0')
const g = Math.round(color.g * 255)
.toString(16)
.padStart(2, '0')
const b = Math.round(color.b * 255)
.toString(16)
.padStart(2, '0')
return `#${r}${g}${b}`
}
generateButtonComponent()
# .github/workflows/sync-figma.yml
name: Sync Figma Design Tokens
on:
schedule:
- cron: '0 9 * * *' # Every day at 9am
workflow_dispatch: # Manual trigger
jobs:
sync:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 18
- run: npm install
- name: Sync design tokens
env:
FIGMA_ACCESS_TOKEN: ${{ secrets.FIGMA_ACCESS_TOKEN }}
run: npm run sync:design-tokens
- name: Create Pull Request
uses: peter-evans/create-pull-request@v5
with:
title: 'chore: sync design tokens from Figma'
body: 'Automated sync of design tokens from Figma'
branch: 'figma/sync-tokens'
commit-message: 'chore: sync design tokens'
{
"scripts": {
"sync:design-tokens": "tsx scripts/sync-design-tokens.ts",
"export:icons": "tsx scripts/export-icons.ts",
"generate:icons": "tsx scripts/generate-icon-components.ts",
"figma:sync-all": "npm run sync:design-tokens && npm run generate:icons"
}
}
Structure:
Design System File
├── 📄 Cover (description)
├── 🎨 Colors (all color styles)
├── 📝 Typography (all text styles)
├── 📏 Spacing (spacing guide)
├── 🧩 Components
│ ├── Buttons
│ ├── Forms
│ └── Cards
└── 🖼️ Icons (all icons in one frame)
Colors:
Primary/500
Secondary/500
Neutral/100
Neutral/900
Success
Error
Typography:
Heading/Large
Heading/Medium
Body/Regular
Body/Small
Components:
Button/Primary
Button/Secondary
Card/Default
Card/Elevated
Figma now supports variables natively. Use them for:
Extract these automatically with the API.
// Check for Figma updates
async function checkForUpdates() {
const client = new FigmaClient()
const fileKey = 'YOUR_FIGMA_FILE_KEY'
const file = await client.getFile(fileKey)
const currentVersion = file.version
// Store in database or file
const previousVersion = await getPreviousVersion()
if (currentVersion !== previousVersion) {
console.log('Figma file updated!')
console.log(`Version: ${previousVersion} → ${currentVersion}`)
// Trigger sync
await syncDesignTokens()
await savePreviousVersion(currentVersion)
} else {
console.log('No updates')
}
}
// 1. Extract tokens
const tokens = await client.extractDesignTokens(fileKey)
// 2. Generate CSS variables
const css = generateCSS(tokens)
// 3. Generate TypeScript types
const types = `
export type ColorToken =
${tokens.colors.map(c => `| '${c.name}'`).join('\n ')}
export type SpacingToken =
${tokens.spacing.map(s => `| '${s.name}'`).join('\n ')}
`.trim()
await fs.writeFile('src/types/tokens.ts', types)
// 4. Use in components
import { ColorToken } from '@/types/tokens'
interface ButtonProps {
color: ColorToken
}
// Keep components in sync with Figma
async function syncComponent(componentName: string) {
const client = new FigmaClient()
const fileKey = 'YOUR_FIGMA_FILE_KEY'
// Get component from Figma
const components = await client.getFileComponents(fileKey)
const component = Object.values(components).find(c => c.name === componentName)
if (!component) {
throw new Error(`Component not found: ${componentName}`)
}
// Generate code
const code = await generateComponentCode(component)
// Write to file
await fs.writeFile(`components/${componentName}.tsx`, code)
console.log(`Synced: ${componentName}`)
}
Problem: Figma style names have spaces/special characters
Solution: Normalize names
function normalizeTokenName(name: string): string {
return name
.toLowerCase()
.replace(/[^a-z0-9]+/g, '-')
.replace(/^-|-$/g, '')
}
Problem: RGB values need conversion
Solution: Use proper color space conversion
function rgbToHex(color: { r: number; g: number; b: number }): string {
const r = Math.round(color.r * 255)
const g = Math.round(color.g * 255)
const b = Math.round(color.b * 255)
return `#${r.toString(16).padStart(2, '0')}${g.toString(16).padStart(2, '0')}${b.toString(16).padStart(2, '0')}`
}
Problem: Too many requests
Solution: Cache responses
const cache = new Map<string, { data: any; timestamp: number }>()
async function getCachedFile(fileKey: string) {
const cached = cache.get(fileKey)
if (cached && Date.now() - cached.timestamp < 60000) {
return cached.data
}
const file = await client.getFile(fileKey)
cache.set(fileKey, { data: file, timestamp: Date.now() })
return file
}
Figma Plugins:
Libraries:
@figma/rest-api-spec - TypeScript typesfigma-api - Alternative clientstyle-dictionary - Transform tokensRelated Skills:
design-system-architect - Building design systemsvisual-designer - Design principlesasset-manager - Managing exported assetsTurn designs into code, automatically. 🎨→💻