Figma Integration and Design Systems
Bridge the gap between design and development by integrating Figma with your Nuxt UI components for consistent design implementation.
Why Figma Integration
- ✅ Maintain design-dev consistency
- ✅ Automate design token extraction
- ✅ Reduce manual translation errors
- ✅ Speed up development workflow
- ✅ Enable designer-developer collaboration
Design Tokens from Figma
Figma Tokens Plugin
Install the Figma Tokens plugin in Figma to export design tokens.
Export Tokens
// design-tokens.json (exported from Figma)
{
"colors": {
"primary": {
"50": { "value": "#eff6ff", "type": "color" },
"100": { "value": "#dbeafe", "type": "color" },
"500": { "value": "#3b82f6", "type": "color" },
"900": { "value": "#1e3a8a", "type": "color" }
},
"semantic": {
"success": { "value": "{colors.green.500}", "type": "color" },
"error": { "value": "{colors.red.500}", "type": "color" },
"warning": { "value": "{colors.yellow.500}", "type": "color" }
}
},
"spacing": {
"xs": { "value": "4", "type": "spacing" },
"sm": { "value": "8", "type": "spacing" },
"md": { "value": "16", "type": "spacing" },
"lg": { "value": "24", "type": "spacing" },
"xl": { "value": "32", "type": "spacing" }
},
"typography": {
"fontFamily": {
"sans": { "value": "Inter, sans-serif", "type": "fontFamilies" },
"mono": { "value": "JetBrains Mono, monospace", "type": "fontFamilies" }
},
"fontSize": {
"xs": { "value": "12", "type": "fontSizes" },
"sm": { "value": "14", "type": "fontSizes" },
"base": { "value": "16", "type": "fontSizes" },
"lg": { "value": "18", "type": "fontSizes" },
"xl": { "value": "20", "type": "fontSizes" }
},
"fontWeight": {
"normal": { "value": "400", "type": "fontWeights" },
"medium": { "value": "500", "type": "fontWeights" },
"semibold": { "value": "600", "type": "fontWeights" },
"bold": { "value": "700", "type": "fontWeights" }
}
},
"borderRadius": {
"none": { "value": "0", "type": "borderRadius" },
"sm": { "value": "4", "type": "borderRadius" },
"md": { "value": "8", "type": "borderRadius" },
"lg": { "value": "12", "type": "borderRadius" },
"full": { "value": "9999", "type": "borderRadius" }
}
}
Transform Tokens to CSS Variables
// scripts/transform-tokens.ts
import fs from 'fs'
import tokens from './design-tokens.json'
function transformTokens(obj: any, prefix = ''): string {
let css = ''
for (const [key, value] of Object.entries(obj)) {
const fullKey = prefix ? `${prefix}-${key}` : key
if (typeof value === 'object' && 'value' in value) {
// Leaf node - actual token value
const tokenValue = resolveReference(value.value)
css += ` --${fullKey}: ${tokenValue};\n`
} else if (typeof value === 'object') {
// Nested object
css += transformTokens(value, fullKey)
}
}
return css
}
function resolveReference(value: string): string {
// Resolve references like {colors.primary.500}
if (typeof value === 'string' && value.startsWith('{')) {
const path = value.slice(1, -1).split('.')
let current: any = tokens
for (const key of path) {
current = current[key]
}
return current.value
}
return value
}
const css = `:root {\n${transformTokens(tokens)}}
`
fs.writeFileSync('./assets/css/design-tokens.css', css)
console.log('Design tokens transformed!')
Generated CSS Variables
/* assets/css/design-tokens.css */
:root {
--colors-primary-50: #eff6ff;
--colors-primary-100: #dbeafe;
--colors-primary-500: #3b82f6;
--colors-primary-900: #1e3a8a;
--spacing-xs: 4px;
--spacing-sm: 8px;
--spacing-md: 16px;
--spacing-lg: 24px;
--spacing-xl: 32px;
--typography-fontFamily-sans: Inter, sans-serif;
--typography-fontSize-base: 16px;
--typography-fontWeight-normal: 400;
--borderRadius-sm: 4px;
--borderRadius-md: 8px;
--borderRadius-lg: 12px;
}
Use in Nuxt
// nuxt.config.ts
export default defineNuxtConfig({
css: ['~/assets/css/design-tokens.css']
})
UnoCSS Integration with Figma Tokens
Install UnoCSS
npm install -D @unocss/nuxt
Configure with Design Tokens
// uno.config.ts
import { defineConfig } from 'unocss'
import tokens from './design-tokens.json'
export default defineConfig({
theme: {
colors: {
primary: {
50: tokens.colors.primary['50'].value,
100: tokens.colors.primary['100'].value,
500: tokens.colors.primary['500'].value,
900: tokens.colors.primary['900'].value
}
},
spacing: {
xs: `${tokens.spacing.xs.value}px`,
sm: `${tokens.spacing.sm.value}px`,
md: `${tokens.spacing.md.value}px`,
lg: `${tokens.spacing.lg.value}px`,
xl: `${tokens.spacing.xl.value}px`
},
borderRadius: {
sm: `${tokens.borderRadius.sm.value}px`,
md: `${tokens.borderRadius.md.value}px`,
lg: `${tokens.borderRadius.lg.value}px`
}
}
})
Use in Components
<template>
<button class="bg-primary-500 text-white px-md py-sm rounded-md">
Click me
</button>
</template>
Component Synchronization
Figma to Vue Component
Use the Figma to Code plugin to export Figma components.
Manual Component Creation
<!-- components/ui/Button.vue -->
<script setup lang="ts">
interface Props {
variant?: 'primary' | 'secondary' | 'outline'
size?: 'sm' | 'md' | 'lg'
disabled?: boolean
}
const props = withDefaults(defineProps<Props>(), {
variant: 'primary',
size: 'md',
disabled: false
})
const buttonClasses = computed(() => {
const base = 'inline-flex items-center justify-center font-medium transition-colors'
const variants = {
primary: 'bg-primary-500 text-white hover:bg-primary-600',
secondary: 'bg-secondary-500 text-white hover:bg-secondary-600',
outline: 'border-2 border-primary-500 text-primary-500 hover:bg-primary-50'
}
const sizes = {
sm: 'text-sm px-3 py-1.5 rounded-sm',
md: 'text-base px-4 py-2 rounded-md',
lg: 'text-lg px-6 py-3 rounded-lg'
}
return [
base,
variants[props.variant],
sizes[props.size],
props.disabled && 'opacity-50 cursor-not-allowed'
].filter(Boolean).join(' ')
})
</script>
<template>
<button
:class="buttonClasses"
:disabled="disabled"
>
<slot />
</button>
</template>
Designer-Developer Workflow
Setup Process
- Design in Figma
- Create components with consistent naming
- Use Auto Layout for responsive designs
- Define variants (hover, active, disabled states)
- Apply design tokens throughout
- Export Design Tokens
- Use Figma Tokens plugin
- Export as JSON
- Version control tokens file
- Transform to Code
- Run transformation script
- Generate CSS variables or config
- Update UnoCSS/Tailwind config
- Build Components
- Match Figma component structure
- Implement all variants
- Add TypeScript types
- Test all states
- Review & Iterate
- Designer reviews implementation
- Adjust as needed
- Update documentation
Automation Script
// scripts/sync-design-tokens.ts
import { exec } from 'child_process'
import { promisify } from 'util'
import fetch from 'node-fetch'
const execAsync = promisify(exec)
async function syncDesignTokens() {
console.log('Fetching design tokens from Figma...')
// Use Figma API to fetch tokens
const FIGMA_TOKEN = process.env.FIGMA_TOKEN
const FILE_KEY = process.env.FIGMA_FILE_KEY
const response = await fetch(
`https://api.figma.com/v1/files/${FILE_KEY}`,
{
headers: {
'X-Figma-Token': FIGMA_TOKEN
}
}
)
const data = await response.json()
// Extract tokens from Figma file
// Transform to design-tokens.json
// ... token extraction logic
console.log('Running transformation...')
await execAsync('tsx scripts/transform-tokens.ts')
console.log('Design tokens synced!')
}
syncDesignTokens()
Package Script
// package.json
{
"scripts": {
"sync:tokens": "tsx scripts/sync-design-tokens.ts",
"sync:tokens:watch": "nodemon --watch design-tokens.json --exec npm run sync:tokens"
}
}
Visual Consistency Checks
Component Documentation
<!-- components/ui/Button.stories.vue -->
<script setup lang="ts">
const variants = ['primary', 'secondary', 'outline']
const sizes = ['sm', 'md', 'lg']
</script>
<template>
<div class="stories">
<h1>Button Component</h1>
<section>
<h2>Variants</h2>
<div class="flex gap-4">
<UiButton
v-for="variant in variants"
:key="variant"
:variant="variant"
>
{{ variant }}
</UiButton>
</div>
</section>
<section>
<h2>Sizes</h2>
<div class="flex gap-4 items-center">
<UiButton
v-for="size in sizes"
:key="size"
:size="size"
>
{{ size }}
</UiButton>
</div>
</section>
<section>
<h2>States</h2>
<div class="flex gap-4">
<UiButton>Default</UiButton>
<UiButton disabled>Disabled</UiButton>
</div>
</section>
</div>
</template>
Percy Visual Testing
npm install -D @percy/cli @percy/puppeteer
// tests/visual/button.spec.js
import puppeteer from 'puppeteer'
import percySnapshot from '@percy/puppeteer'
describe('Button Visual Tests', () => {
let browser, page
beforeAll(async () => {
browser = await puppeteer.launch()
page = await browser.newPage()
})
afterAll(async () => {
await browser.close()
})
test('captures button variants', async () => {
await page.goto('http://localhost:3000/stories/button')
await percySnapshot(page, 'Button Variants')
})
})
Design System Documentation
Auto-generated Docs
<!-- pages/design-system/index.vue -->
<script setup lang="ts">
import designTokens from '~/design-tokens.json'
const colorGroups = computed(() => {
return Object.entries(designTokens.colors).map(([name, shades]) => ({
name,
shades: Object.entries(shades).map(([shade, token]) => ({
shade,
value: token.value
}))
}))
})
</script>
<template>
<div class="design-system">
<h1>Design System</h1>
<section>
<h2>Colors</h2>
<div
v-for="group in colorGroups"
:key="group.name"
class="color-group"
>
<h3>{{ group.name }}</h3>
<div class="color-swatches">
<div
v-for="shade in group.shades"
:key="shade.shade"
class="swatch"
>
<div
class="color-preview"
:style="{ backgroundColor: shade.value }"
/>
<div class="color-info">
<div class="shade">{{ shade.shade }}</div>
<div class="value">{{ shade.value }}</div>
</div>
</div>
</div>
</div>
</section>
</div>
</template>
<style scoped>
.color-swatches {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
gap: 1rem;
}
.swatch {
border: 1px solid #e5e7eb;
border-radius: 8px;
overflow: hidden;
}
.color-preview {
height: 80px;
}
.color-info {
padding: 0.5rem;
font-size: 0.875rem;
}
</style>
Best Practices
✅ Do's
- ✅ Use design tokens as single source of truth
- ✅ Automate token synchronization
- ✅ Version control design tokens
- ✅ Document all component variants
- ✅ Use semantic naming conventions
- ✅ Implement visual regression testing
- ✅ Keep designers in the loop
❌ Don'ts
- ❌ Don't hardcode values in components
- ❌ Don't ignore design system guidelines
- ❌ Don't create components without Figma counterpart
- ❌ Don't skip documentation
- ❌ Don't forget responsive variants
- ❌ Don't mix different design systems
Advanced Integration
Figma API Integration
// utils/figma-api.ts
export class FigmaAPI {
private token: string
private baseURL = 'https://api.figma.com/v1'
constructor(token: string) {
this.token = token
}
async getFile(fileKey: string) {
const response = await fetch(
`${this.baseURL}/files/${fileKey}`,
{
headers: {
'X-Figma-Token': this.token
}
}
)
return response.json()
}
async getStyles(fileKey: string) {
const response = await fetch(
`${this.baseURL}/files/${fileKey}/styles`,
{
headers: {
'X-Figma-Token': this.token
}
}
)
return response.json()
}
async getComponents(fileKey: string) {
const response = await fetch(
`${this.baseURL}/files/${fileKey}/components`,
{
headers: {
'X-Figma-Token': this.token
}
}
)
return response.json()
}
}
CI/CD Token Sync
# .github/workflows/sync-tokens.yml
name: Sync Design Tokens
on:
schedule:
- cron: '0 0 * * *' # Daily
workflow_dispatch:
jobs:
sync:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Node
uses: actions/setup-node@v3
with:
node-version: '18'
- name: Install dependencies
run: npm ci
- name: Sync tokens
env:
FIGMA_TOKEN: ${{ secrets.FIGMA_TOKEN }}
FIGMA_FILE_KEY: ${{ secrets.FIGMA_FILE_KEY }}
run: npm run sync:tokens
- name: Create PR if changed
uses: peter-evans/create-pull-request@v5
with:
commit-message: 'chore: sync design tokens from Figma'
title: 'Sync Design Tokens'
branch: 'sync-design-tokens'
Integrating Figma with Nuxt creates a seamless design-to-development workflow. Automate design token extraction and maintain visual consistency across your application.