デザインシステムとAIの融合は、現代的な開発組織の最前線です。UI Stack・AutoLayout・Variables・VRTを体系的に統合することで、デザイナー・エンジニア・AIエージェントが完全に調和した開発体制が実現します。本ガイドは、その完全な実装パターンと運用哲学を解説します。
UI Stack とは:5つの状態管理
UI Stack は Scott Hurff が提唱した設計思想で、すべての UI は以下の 5 つの状態を必ず持つという考え方です:
- Ideal State(理想状態): データが充分にあり、すべてが正常に機能している状態
- Loading State(読み込み状態): データ取得中で、スケルトンやスピナーが表示されている状態
- Empty State(空状態): データが存在しない、または初期化時の状態
- Error State(エラー状態): API呼び出しやバリデーション失敗時の状態
- Partial State(部分的状態): 一部データのみ利用可能、または段階的読み込み中の状態
Ideal State の実装パターン
// components/ProductCard.tsx
interface ProductCardProps {
product: Product;
isLoading?: false;
isEmpty?: false;
error?: undefined;
}
export const ProductCard: React.FC<ProductCardProps> = ({ product }) => {
return (
<div style={{ padding: '12px', borderRadius: '8px', border: '1px solid #E5E7EB' }}>
<img src={product.image} alt={product.name} />
<h3>{product.name}</h3>
<p>{product.description}</p>
<span style={{ color: '#2563EB', fontWeight: 'bold' }}>
${product.price.toFixed(2)}
</span>
</div>
);
};
Loading State の実装パターン
interface ProductCardProps {
product?: undefined;
isLoading: true;
isEmpty?: false;
error?: undefined;
}
export const ProductCardSkeleton: React.FC = () => {
return (
<div style={{ padding: '12px', borderRadius: '8px', backgroundColor: '#F3F4F6' }}>
<div style={{ width: '100%', height: '200px', backgroundColor: '#E5E7EB', borderRadius: '4px' }} />
<div style={{ marginTop: '8px', height: '20px', backgroundColor: '#E5E7EB', borderRadius: '4px' }} />
<div style={{ marginTop: '8px', height: '16px', backgroundColor: '#E5E7EB', width: '80%', borderRadius: '4px' }} />
</div>
);
};
Empty State の実装パターン
interface ProductCardProps {
product?: undefined;
isLoading?: false;
isEmpty: true;
error?: undefined;
}
export const ProductCardEmpty: React.FC = () => {
return (
<div style={{ textAlign: 'center', padding: '32px 12px' }}>
<p style={{ color: '#6B7280', fontSize: '14px' }}>
商品がまだ登録されていません。
</p>
<p style={{ color: '#9CA3AF', fontSize: '12px' }}>
新しい商品を追加してください。
</p>
</div>
);
};
Error State の実装パターン
interface ProductCardProps {
product?: undefined;
isLoading?: false;
isEmpty?: false;
error: Error;
}
export const ProductCardError: React.FC<{ error: Error }> = ({ error }) => {
return (
<div style={{ padding: '12px', borderRadius: '8px', backgroundColor: '#FEE2E2', borderLeft: '4px solid #EF4444' }}>
<p style={{ color: '#991B1B', fontWeight: 'bold' }}>エラーが発生しました</p>
<p style={{ color: '#7F1D1D', fontSize: '14px' }}>
{error.message}
</p>
<button onClick={() => window.location.reload()} style={{ marginTop: '8px', padding: '6px 12px', backgroundColor: '#EF4444', color: '#FFFFFF', borderRadius: '4px', border: 'none', cursor: 'pointer' }}>
再試行
</button>
</div>
);
};
Partial State の実装パターン
interface ProductCardProps {
product: Partial<Product>;
isLoading?: false;
isEmpty?: false;
error?: undefined;
}
export const ProductCardPartial: React.FC<{ product: Partial<Product> }> = ({ product }) => {
return (
<div style={{ padding: '12px', borderRadius: '8px', border: '1px solid #FCD34D' }}>
{product.image && <img src={product.image} alt={product.name} />}
<h3>{product.name || '読み込み中...'}</h3>
{product.description ? <p>{product.description}</p> : <p style={{ color: '#9CA3AF' }}>説明を読み込み中...</p>}
</div>
);
};
VRT(Visual Regression Testing)活用:UI Stack とログイン状態の掛け合わせ
VRT は、UI の見た目が意図した通りか、レグレッションがないかを自動検証するテスト手法です。UI Stack の 5 状態に加えて、ログイン状態・言語設定・ダークモード等の掛け合わせを考慮すると、テストシナリオは組み合わせ的に増加します。
VRT テストシナリオ設計例
ProductCard コンポーネント
├─ Ideal State
│ ├─ 日本語 × Light モード
│ ├─ 日本語 × Dark モード
│ ├─ English × Light モード
│ └─ English × Dark モード
├─ Loading State
│ ├─ Light モード
│ └─ Dark モード
├─ Empty State
│ ├─ Light モード
│ └─ Dark モード
├─ Error State
│ ├─ Light モード
│ └─ Dark モード
└─ Partial State
├─ Light モード
└─ Dark モード
計 14 パターン
Playwright + Percy(VRT SaaS)による自動化
// e2e/product-card.spec.ts
import { test } from '@playwright/test';
import percySnapshot from '@percy/playwright';
test.describe('ProductCard VRT Suite', () => {
test('Ideal State - Light Mode JP', async ({ page }) => {
await page.goto('/components/product-card?state=ideal&lang=ja&theme=light');
await percySnapshot(page, 'ProductCard-Ideal-Light-JP');
});
test('Loading State - Dark Mode', async ({ page }) => {
await page.goto('/components/product-card?state=loading&theme=dark');
await percySnapshot(page, 'ProductCard-Loading-Dark');
});
test('Error State - Light Mode', async ({ page }) => {
await page.goto('/components/product-card?state=error&theme=light');
await percySnapshot(page, 'ProductCard-Error-Light');
});
});
CI/CD パイプラインに統合すれば、毎回の PR 時に視覚的差分が自動検出される仕組みになります。
AutoLayout の実践的構築パターン
Figma 内で丁寧に構築された AutoLayout は、AI コード生成の精度を大幅に向上させます。実装パターンを示します:
Spacing・Padding・Alignment の標準化
Frame: "Container"
├─ Direction: vertical
├─ Padding: { top: 16, right: 16, bottom: 16, left: 16 }
├─ Gap: 12
├─ Alignment: { horizontal: 'center', vertical: 'start' }
└─ Children
├─ Frame: "Header" (AutoLayout horizontal, gap: 8)
│ ├─ Component: "Logo"
│ └─ Text: "Title"
├─ Frame: "Content" (AutoLayout vertical, gap: 16)
│ ├─ Component: "Card" × 3
│ └─ Text: "Footer"
このように階層的に AutoLayout を組むと、AI は「親が vertical、子が horizontal」という構造を認識でき、正確な Flexbox コードを生成します。
Variables のバインド戦略:3 層の統合
Variables は以下の 3 層で組織化するのが最適です:
1. Semantic レベル
デザイン意図を反映した抽象名:
Semantic/
├─ Primary/Foreground: #2563EB
├─ Primary/Background: #EFF6FF
├─ Destructive/Foreground: #EF4444
├─ Destructive/Background: #FEE2E2
├─ Success/Foreground: #16A34A
└─ Success/Background: #DCFCE7
2. Action レベル
UI 要素の役割に基づいた変数:
Action/
├─ Button/Primary/Background: Semantic/Primary/Foreground
├─ Button/Primary/Text: #FFFFFF
├─ Button/Secondary/Background: Semantic/Primary/Background
├─ Button/Secondary/Text: Semantic/Primary/Foreground
├─ Padding/Small: 8px
├─ Padding/Medium: 12px
└─ Border/Radius: 8px
3. Component レベル
個別コンポーネント内の上書き(限定的に):
ProductCard/
├─ Background: #FFFFFF
├─ Border/Color: #E5E7EB
└─ Shadow/Blur: 4px
AI はこの階層構造を読み込むと、CSS 変数やデザイントークンとして正確に翻訳できます。
アノテーション vs コメント:仕様記録と対話的やりとり
Figma には 2 つの情報記録方法があります:
アノテーション:仕様書的記録
「このボタンは XXX の時に disabled になる」など、不変な仕様を記録。AI はこれを読み込んで実装条件を推測します。
Button コンポーネント内アノテーション:
"Disabled when form is invalid or API request is pending.
Click triggers POST /api/submit with formData."
コメント:対話的やりとり
「この margin は 16px に変更する予定」など、改善案や未決定事項。デザイナーとエンジニアのやりとり用。
Figma では、コメントは version history に記録されます。対話を後から参照できるため、設計判断の履歴が残ります。
ベストプラクティス:アノテーションで「何をすべきか」を定める → コメントで「なぜそうするのか」を議論 → 決定後アノテーションに統合
デザイン制作の委譲事例:エンジニアへの Figma 制作委譲
従来型では「デザイナーが 100% 制作」でしたが、AI 時代には以下のようなモデルも可能です:
モデル 1: デザイナー主導 + エンジニア確認
- デザイナーが Figma でコンポーネント・Variabledefined を完成
- エンジニアが Dev Mode で読み込み、実装可能性をレビュー
- 不可能な場合はコメントで「この margin は 1px 単位で調整が難しい」と記録
- デザイナーが AutoLayout/Variables を修正
モデル 2: エンジニア支援制作
- 基本デザインはデザイナーが提案
- UI Stack の各状態実装は、エンジニア(AI エージェント支援)が Figma 内で直接制作
- デザイナーがスタイル確認・承認
- デザイナーと実装が完全に同期された状態に
モデル 2 では、チーム内の齟齬が最小化され、修正ループが劇的に短縮されます。
AI エージェント(Antigravity)にデザインシステムを理解させる方法
Antigravity IDE では、Figma MCP とデザイントークンJSONを組み合わせて、エージェントにデザイン仕様を「理解」させることができます。
Step 1: デザイントークン JSON エクスポート
Figma Variables から、以下の形式でトークン JSON をエクスポート:
{
"semantic": {
"primary": {
"foreground": "#2563EB",
"background": "#EFF6FF"
}
},
"action": {
"button": {
"primary": {
"background": "{semantic.primary.foreground}",
"text": "#FFFFFF"
}
},
"spacing": {
"small": "8px",
"medium": "12px"
}
}
}
Step 2: エージェントコンテキストに統合
Antigravity IDE の MCP 設定で、トークン JSON をコンテキストに含める:
{
"mcpServers": {
"design-tokens": {
"command": "node",
"args": ["scripts/design-tokens-mcp.js"],
"env": {
"TOKENS_FILE": "design-tokens.json"
}
}
}
}
Step 3: エージェントへの指示
IDE チャットで以下のように指示:
ユーザー入力: "ProductCard コンポーネントの Loading State を実装して"
エージェント内の処理:
1. Figma MCP で ProductCard の Loading State フレーム情報を取得
2. design-tokens MCP でカラー・スペーシング定数を参照
3. Figma の AutoLayout 構造を認識
4. React コンポーネント生成(トークンを CSS 変数として適用)
エージェントがデザイン仕様を「理解」した状態で実装生成するため、修正が極めて少なくなります。
マルチエージェント パイプライン:デザイン → 実装 → テスト の自動化
AI 時代には、単一エージェントではなく、複数エージェントの合作が可能です。
パイプライン設計
Input: Figma デザインファイル(Dev Mode + Variables)
↓
[Agent 1: 実装エージェント]
→ React/SwiftUI コンポーネント生成
→ 出力: src/components/{name}.tsx
↓
[Agent 2: テストエージェント]
→ Playwright + Percy VRT テスト自動生成
→ 出力: e2e/{name}.spec.ts
↓
[Agent 3: ドキュメント エージェント]
→ Storybook stories 自動生成
→ Props 説明・使用例の自動記述
→ 出力: stories/{name}.stories.tsx
↓
[Agent 4: レビュー エージェント]
→ 生成されたコードが UI Stack の 5 状態を満たすか確認
→ TypeScript 型安全性の検証
→ Accessibility(a11y)チェック
↓
Output: 実装完了 → CI/CD パイプラインへ
各エージェントが専門領域で最適化されており、人間は高度な判断のみに集中できます。
デザイントークン → コード への変換実装パターン
Figma Variables を CSS/TypeScript に変換する実装例:
// scripts/build-design-tokens.ts
import figmaAPI from 'figma-api';
interface DesignTokens {
colors: Record<string, string>;
spacing: Record<string, string>;
typography: Record<string, Record<string, string>>;
}
async function extractFigmaVariables(): Promise<DesignTokens> {
const client = new figmaAPI.Api({ personalAccessToken: process.env.FIGMA_TOKEN });
const file = await client.getFile(process.env.FIGMA_FILE_ID);
const tokens: DesignTokens = { colors: {}, spacing: {}, typography: {} };
// Variables を走査して抽出
file.styles.forEach((style) => {
if (style.name.startsWith('Semantic/')) {
tokens.colors[style.name] = style.value;
}
});
return tokens;
}
async function generateCSSVariables(tokens: DesignTokens) {
let css = ':root {\n';
Object.entries(tokens.colors).forEach(([name, value]) => {
const cssVarName = name.replace(/\//g, '-').toLowerCase();
css += ` --color-${cssVarName}: ${value};\n`;
});
css += '}\n';
return css;
}
// Usage in build
const tokens = await extractFigmaVariables();
const css = await generateCSSVariables(tokens);
// → output to src/styles/design-tokens.css
VRT 自動化のCI/CDパイプライン構築
GitHub Actions + Percy による完全自動化:
# .github/workflows/vrt.yml
name: Visual Regression Testing
on: [pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: '18'
- run: npm ci
- name: Install Playwright browsers
run: npx playwright install --with-deps
- name: Run VRT tests with Percy
run: npx percy exec -- npm run test:e2e
env:
PERCY_TOKEN: ${{ secrets.PERCY_TOKEN }}
- name: Upload Percy results
if: always()
run: |
echo "Percy VRT Results: https://percy.io/builds/${PERCY_BUILD_ID}"
PR ごとに VRT が自動実行され、ビジュアル差分が検出されたら、Percy 上で視覚的に確認できます。
コード例:完全な ProductCard コンポーネント実装
// components/ProductCard.tsx
import React, { CSSProperties } from 'react';
interface Product {
id: string;
name: string;
description: string;
image: string;
price: number;
}
type ProductCardState = 'ideal' | 'loading' | 'empty' | 'error' | 'partial';
interface ProductCardProps {
product?: Product;
state: ProductCardState;
onRetry?: () => void;
}
const DESIGN_TOKENS = {
colors: {
primary: '#2563EB',
destructive: '#EF4444',
background: '#FFFFFF',
border: '#E5E7EB',
textPrimary: '#1F2937',
textSecondary: '#6B7280',
errorBg: '#FEE2E2',
errorBorder: '#EF4444',
},
spacing: {
xs: '8px',
sm: '12px',
md: '16px',
lg: '24px',
},
};
const containerStyle: CSSProperties = {
padding: DESIGN_TOKENS.spacing.sm,
borderRadius: '8px',
border: `1px solid ${DESIGN_TOKENS.colors.border}`,
backgroundColor: DESIGN_TOKENS.colors.background,
display: 'flex',
flexDirection: 'column',
gap: DESIGN_TOKENS.spacing.sm,
};
export const ProductCard: React.FC<ProductCardProps> = ({
product,
state,
onRetry,
}) => {
if (state === 'loading') {
return (
<div style={containerStyle}>
<div style={{
width: '100%',
height: '200px',
backgroundColor: '#F3F4F6',
borderRadius: '4px',
}} />
<div style={{ height: '20px', backgroundColor: '#E5E7EB', borderRadius: '4px' }} />
</div>
);
}
if (state === 'empty') {
return (
<div style={{ ...containerStyle, textAlign: 'center', color: DESIGN_TOKENS.colors.textSecondary }}>
<p>商品がまだ登録されていません。</p>
</div>
);
}
if (state === 'error') {
return (
<div style={{ ...containerStyle, backgroundColor: DESIGN_TOKENS.colors.errorBg, borderColor: DESIGN_TOKENS.colors.errorBorder }}>
<p style={{ color: DESIGN_TOKENS.colors.destructive, fontWeight: 'bold' }}>エラーが発生しました</p>
{onRetry && (
<button onClick={onRetry} style={{
padding: `${DESIGN_TOKENS.spacing.xs} ${DESIGN_TOKENS.spacing.sm}`,
backgroundColor: DESIGN_TOKENS.colors.destructive,
color: '#FFFFFF',
border: 'none',
borderRadius: '4px',
cursor: 'pointer',
}}>
再試行
</button>
)}
</div>
);
}
if (!product || state === 'partial') {
return (
<div style={containerStyle}>
{product?.image && <img src={product.image} alt={product.name} style={{ width: '100%', borderRadius: '4px' }} />}
<h3 style={{ margin: 0, color: DESIGN_TOKENS.colors.textPrimary }}>{product?.name || '読み込み中...'}</h3>
</div>
);
}
// Ideal State
return (
<div style={containerStyle}>
<img src={product.image} alt={product.name} style={{ width: '100%', borderRadius: '4px' }} />
<h3 style={{ margin: 0, color: DESIGN_TOKENS.colors.textPrimary }}>{product.name}</h3>
<p style={{ margin: 0, color: DESIGN_TOKENS.colors.textSecondary, fontSize: '14px' }}>{product.description}</p>
<span style={{ color: DESIGN_TOKENS.colors.primary, fontWeight: 'bold', fontSize: '18px' }}>
${product.price.toFixed(2)}
</span>
</div>
);
};
ここまでの要点
デザインシステム × AI開発の統合は、単なるツール導入ではなく、組織の開発哲学の進化です。UI Stack で状態を網羅的に管理し、AutoLayout・Variables で仕様を明確化し、VRT で品質を絶対視する運用体制。その上に AI エージェントを乗せることで、人間は高度な設計・判断に集中でき、反復開発が驚くほど加速します。
デザインシステムは「作品」として、エンジニアリングと同等の丁寧さで磨く。その先に、真のスケーラブルな開発体制が見えています。
個人開発者の視点から(実体験メモ)
参考資源