mcpField
F
Figma Developer

Figma Developer

github.com/daffy0208デザイン・UI38149 回インストール44 週間前に更新
リポジトリ

Figma Developer について

Figma Developerは、Figmaのデザインをコードに変換する手順をまとめたスキルです。デザイントークンやコンポーネント構造の抽出、コンポーネント生成、同期ワークフローの自動化を扱います。

主な機能

  1. 01アクセストークンでFigmaに接続
  2. 02デザイントークンの抽出
  3. 03トークンをコードとして出力
  4. 04コンポーネントの抽出と生成
  5. 05デザイン同期ワークフローの自動化

活用シーン

  1. 01Figmaデザインからコード用トークンを作成
  2. 02FigmaファイルからUIコンポーネントを生成
  3. 03デザイントークンとコンポーネントを同期

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/daffy0208/ai-dev-standards --skill figma-developer

インストール後、Claude Code で/figma-developerを入力して読み込まれたか確認します。

同梱ファイルmanifest.yaml

詳細

Figma 開発者

Figma のデザインを、本番環境で使えるコードに変換します。

基本原則

デザインを唯一の信頼できる情報源とします。

デザイナーは Figma で作業し、開発者は Figma をもとに構築します。その間をつなぐ作業は、手作業ではなく自動化するべきです。


フェーズ 1:セットアップと認証

Figma アクセストークンの取得
  1. Figma Settings に移動します
  2. 「Personal access tokens」までスクロールします
  3. 「Generate new token」をクリックします
  4. 名前を付けます(例:「Development」)
  5. コピーして安全に保存します
環境のセットアップ
# .env
FIGMA_ACCESS_TOKEN=figd_...
Figma クライアントのインストール
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)

フェーズ 2:デザイントークンの抽出

デザイントークンとは?

デザイントークンとは、コードとして保存されたデザイン上の決定事項(色、タイポグラフィ、スペーシング)です。

メリット:

  • 唯一の信頼できる情報源
  • プラットフォーム間で一貫性を保てる
  • 更新が簡単
  • 型安全
Figma からトークンを抽出する
// 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;
}

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>
  )
}

フェーズ 3:アセットのエクスポート

アイコンを SVG としてエクスポートする
// 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()
React アイコンコンポーネントを生成する
// 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()

使用例:

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>
  )
}

フェーズ 4:コンポーネントの生成

コンポーネント構造を抽出する
// 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()
Figma からボタンコンポーネントを生成する
// 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()

フェーズ 5:自動化されたワークフロー

GitHub Actions を設定する
# .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'
Package.json スクリプト
{
  "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"
  }
}

ベストプラクティス

1. Figma ファイルを整理する

構成:

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)
2. 命名規則

色:

Primary/500
Secondary/500
Neutral/100
Neutral/900
Success
Error

タイポグラフィ:

Heading/Large
Heading/Medium
Body/Regular
Body/Small

コンポーネント:

Button/Primary
Button/Secondary
Card/Default
Card/Elevated
3. Figma Variables(ベータ版)を使用する

Figma は現在、変数をネイティブでサポートしています。次の項目に使用します。

  • 色
  • スペーシング
  • 角丸
  • タイポグラフィのサイズ

API を使って、これらを自動的に抽出します。

4. バージョン管理
// 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:トークンベースの開発
// 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
}
パターン 2:コンポーネントの同期
// 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}`)
}

トラブルシューティング

問題:トークン名が一致しない

問題: Figma のスタイル名にスペースや特殊文字が含まれている

解決策: 名前を正規化する

function normalizeTokenName(name: string): string {
  return name
    .toLowerCase()
    .replace(/[^a-z0-9]+/g, '-')
    .replace(/^-|-$/g, '')
}
問題:色が異なって見える

問題: RGB 値の変換が必要

解決策: 適切な色空間変換を使用する

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')}`
}
問題:API のレート制限

問題: リクエストが多すぎる

解決策: レスポンスをキャッシュする

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 プラグイン:

  • Figma Tokens - デザイントークンを管理
  • Design Tokens - トークンをエクスポート
  • Figma to Code - コードを生成

ライブラリ:

  • @figma/rest-api-spec - TypeScript の型
  • figma-api - 代替クライアント
  • style-dictionary - トークンを変換

関連スキル:

  • design-system-architect - デザインシステムの構築
  • visual-designer - デザイン原則
  • asset-manager - エクスポートしたアセットの管理

デザインを自動的にコードに変換します。 🎨→💻

コメント