T-CREATOR

Storybook と Design Tokens:ブランド統一の極意

Storybook と Design Tokens:ブランド統一の極意

現代の Web 開発において、ブランドの一貫性を保つことは成功の鍵となります。しかし、複数の開発者が関わるプロジェクトでは、デザインの統一を保つのが困難になることがあります。そんな課題を解決するのが、Storybook と Design Tokens の組み合わせです。

この記事では、Design Tokens の基本概念から、Storybook での実装方法、そしてブランド統一を実現するための実践的なテクニックまで、段階的に解説していきます。開発チーム全体で一貫したデザインシステムを構築し、ブランドの価値を最大化する方法を学んでいきましょう。

Design Tokens とは何か

Design Tokens は、デザインシステムの基盤となる最小単位のデザイン要素です。色、フォント、スペーシング、ボーダー半径などの値を変数として定義し、一貫したデザインを実現するための仕組みです。

Design Tokens の基本構造

Design Tokens は階層構造で管理され、以下のような形で定義されます:

json{
  "color": {
    "primary": {
      "50": "#eff6ff",
      "500": "#3b82f6",
      "900": "#1e3a8a"
    },
    "secondary": {
      "50": "#f8fafc",
      "500": "#64748b",
      "900": "#0f172a"
    }
  },
  "typography": {
    "fontSize": {
      "xs": "0.75rem",
      "sm": "0.875rem",
      "base": "1rem",
      "lg": "1.125rem",
      "xl": "1.25rem"
    },
    "fontWeight": {
      "normal": "400",
      "medium": "500",
      "semibold": "600",
      "bold": "700"
    }
  },
  "spacing": {
    "xs": "0.25rem",
    "sm": "0.5rem",
    "md": "1rem",
    "lg": "1.5rem",
    "xl": "2rem"
  }
}

この構造により、デザインの変更が必要な場合でも、トークンの値を変更するだけで全体に反映されるため、保守性が大幅に向上します。

Design Tokens の種類と役割

Design Tokens には主に以下の種類があります:

  • Color Tokens: ブランドカラー、セマンティックカラー(success、error、warning)
  • Typography Tokens: フォントサイズ、フォントウェイト、行間
  • Spacing Tokens: マージン、パディング、ギャップ
  • Border Tokens: ボーダー半径、ボーダー幅、ボーダーカラー
  • Shadow Tokens: ドロップシャドウ、ボックスシャドウ
  • Animation Tokens: トランジション時間、イージング関数

Storybook で Design Tokens を活用する理由

Storybook は UI コンポーネントの開発・テスト・ドキュメント化を効率化するツールですが、Design Tokens と組み合わせることで、さらに強力なデザインシステムを構築できます。

Storybook と Design Tokens の相乗効果

  1. 一貫性の保証: Design Tokens を使用することで、すべてのコンポーネントで同じデザイン値が使用されます
  2. 変更の容易さ: トークンの値を変更するだけで、関連するすべてのコンポーネントが自動的に更新されます
  3. チーム間の連携: デザイナーと開発者が同じトークンを参照することで、コミュニケーションが円滑になります
  4. 品質の向上: デザインの一貫性が保たれることで、ユーザー体験が向上します

よくある課題と Design Tokens による解決

従来の開発では、以下のような課題が発生していました:

css/* 問題のある従来のアプローチ */
.button-primary {
  background-color: #3b82f6; /* ハードコードされた色 */
  padding: 12px 24px; /* マジックナンバー */
  border-radius: 6px; /* 一貫性のない値 */
}

.button-secondary {
  background-color: #64748b; /* 異なる色の定義方法 */
  padding: 10px 20px; /* 異なるスペーシング */
  border-radius: 4px; /* 異なるボーダー半径 */
}

Design Tokens を使用することで、これらの問題を解決できます:

css/* Design Tokens を使用した改善されたアプローチ */
.button-primary {
  background-color: var(--color-primary-500);
  padding: var(--spacing-md) var(--spacing-lg);
  border-radius: var(--border-radius-md);
}

.button-secondary {
  background-color: var(--color-secondary-500);
  padding: var(--spacing-md) var(--spacing-lg);
  border-radius: var(--border-radius-md);
}

Design Tokens の設計と管理方法

効果的な Design Tokens システムを構築するためには、適切な設計と管理方法が重要です。

Design Tokens の設計原則

  1. セマンティック命名: 用途を明確に示す名前を使用する
  2. 階層構造: 論理的な階層でトークンを整理する
  3. 拡張性: 将来の変更に対応できる柔軟な構造にする
  4. 一貫性: 命名規則と値の定義方法を統一する

推奨される Design Tokens の構造

json{
  "global": {
    "color": {
      "brand": {
        "primary": {
          "value": "#3b82f6",
          "type": "color"
        },
        "secondary": {
          "value": "#64748b",
          "type": "color"
        }
      },
      "semantic": {
        "success": {
          "value": "#10b981",
          "type": "color"
        },
        "error": {
          "value": "#ef4444",
          "type": "color"
        },
        "warning": {
          "value": "#f59e0b",
          "type": "color"
        }
      }
    },
    "typography": {
      "fontFamily": {
        "primary": {
          "value": "Inter, system-ui, sans-serif",
          "type": "fontFamily"
        }
      },
      "fontSize": {
        "xs": {
          "value": "0.75rem",
          "type": "fontSize"
        },
        "sm": {
          "value": "0.875rem",
          "type": "fontSize"
        },
        "base": {
          "value": "1rem",
          "type": "fontSize"
        }
      }
    }
  }
}

Design Tokens の管理ツール

Design Tokens を効率的に管理するために、以下のツールが活用できます:

  1. Style Dictionary: Amazon が開発した Design Tokens 変換ツール
  2. Theo: Salesforce が開発した Design Tokens 管理ツール
  3. Figma Tokens: Figma プラグインで Design Tokens を管理
  4. Design Tokens Studio: ブラウザベースの Design Tokens エディタ

Style Dictionary を使用した Design Tokens 管理

Style Dictionary を使用すると、Design Tokens を複数のプラットフォーム向けに変換できます:

javascript// style-dictionary.config.js
module.exports = {
  source: ['tokens/**/*.json'],
  platforms: {
    css: {
      transformGroup: 'css',
      buildPath: 'dist/css/',
      files: [
        {
          destination: 'tokens.css',
          format: 'css/variables',
        },
      ],
    },
    scss: {
      transformGroup: 'scss',
      buildPath: 'dist/scss/',
      files: [
        {
          destination: '_tokens.scss',
          format: 'scss/variables',
        },
      ],
    },
  },
};

Storybook での Design Tokens 実装手順

Storybook で Design Tokens を実装する具体的な手順を説明します。

1. 必要なパッケージのインストール

まず、必要なパッケージをインストールします:

bashyarn add -D @storybook/addon-styling style-dictionary
yarn add -D @storybook/addon-themes

2. Storybook の設定ファイルの更新

.storybook​/​main.js ファイルを更新して、Design Tokens のサポートを追加します:

javascript// .storybook/main.js
module.exports = {
  stories: ['../src/**/*.stories.@(js|jsx|ts|tsx)'],
  addons: [
    '@storybook/addon-links',
    '@storybook/addon-essentials',
    '@storybook/addon-interactions',
    '@storybook/addon-styling',
    '@storybook/addon-themes',
  ],
  framework: {
    name: '@storybook/react-webpack5',
    options: {},
  },
  docs: {
    autodocs: 'tag',
  },
};

3. Design Tokens の定義

tokens​/​ ディレクトリを作成し、Design Tokens を定義します:

json// tokens/colors.json
{
  "color": {
    "brand": {
      "primary": {
        "50": { "value": "#eff6ff" },
        "100": { "value": "#dbeafe" },
        "500": { "value": "#3b82f6" },
        "600": { "value": "#2563eb" },
        "900": { "value": "#1e3a8a" }
      },
      "secondary": {
        "50": { "value": "#f8fafc" },
        "100": { "value": "#f1f5f9" },
        "500": { "value": "#64748b" },
        "600": { "value": "#475569" },
        "900": { "value": "#0f172a" }
      }
    },
    "semantic": {
      "success": { "value": "#10b981" },
      "error": { "value": "#ef4444" },
      "warning": { "value": "#f59e0b" },
      "info": { "value": "#3b82f6" }
    }
  }
}

4. CSS 変数の生成

Style Dictionary を使用して CSS 変数を生成します:

javascript// scripts/build-tokens.js
const StyleDictionary = require('style-dictionary');

const styleDictionary = StyleDictionary.extend({
  source: ['tokens/**/*.json'],
  platforms: {
    css: {
      transformGroup: 'css',
      buildPath: 'src/styles/',
      files: [
        {
          destination: 'tokens.css',
          format: 'css/variables',
          options: {
            showFileHeader: true,
          },
        },
      ],
    },
  },
});

styleDictionary.buildAllPlatforms();

5. Storybook での Design Tokens の読み込み

.storybook​/​preview.js ファイルで Design Tokens を読み込みます:

javascript// .storybook/preview.js
import '../src/styles/tokens.css';

export const parameters = {
  actions: { argTypesRegex: '^on[A-Z].*' },
  controls: {
    matchers: {
      color: /(background|color)$/i,
      date: /Date$/,
    },
  },
  themes: {
    default: 'light',
    list: [
      {
        name: 'light',
        class: 'theme-light',
        color: '#ffffff',
      },
      {
        name: 'dark',
        class: 'theme-dark',
        color: '#000000',
      },
    ],
  },
};

6. コンポーネントでの Design Tokens の使用

コンポーネントで Design Tokens を使用します:

tsx// src/components/Button/Button.tsx
import React from 'react';
import './Button.css';

interface ButtonProps {
  variant?: 'primary' | 'secondary';
  size?: 'sm' | 'md' | 'lg';
  children: React.ReactNode;
  onClick?: () => void;
}

export const Button: React.FC<ButtonProps> = ({
  variant = 'primary',
  size = 'md',
  children,
  onClick,
}) => {
  return (
    <button
      className={`button button--${variant} button--${size}`}
      onClick={onClick}
    >
      {children}
    </button>
  );
};
css/* src/components/Button/Button.css */
.button {
  border: none;
  border-radius: var(--border-radius-md);
  font-family: var(--font-family-primary);
  font-weight: var(--font-weight-medium);
  cursor: pointer;
  transition: all var(--transition-duration-fast);
}

.button--primary {
  background-color: var(--color-brand-primary-500);
  color: var(--color-white);
}

.button--primary:hover {
  background-color: var(--color-brand-primary-600);
}

.button--secondary {
  background-color: var(--color-brand-secondary-500);
  color: var(--color-white);
}

.button--secondary:hover {
  background-color: var(--color-brand-secondary-600);
}

.button--sm {
  padding: var(--spacing-xs) var(--spacing-sm);
  font-size: var(--font-size-sm);
}

.button--md {
  padding: var(--spacing-sm) var(--spacing-md);
  font-size: var(--font-size-base);
}

.button--lg {
  padding: var(--spacing-md) var(--spacing-lg);
  font-size: var(--font-size-lg);
}

7. Storybook ストーリーの作成

Design Tokens を使用したコンポーネントのストーリーを作成します:

tsx// src/components/Button/Button.stories.tsx
import type { Meta, StoryObj } from '@storybook/react';
import { Button } from './Button';

const meta: Meta<typeof Button> = {
  title: 'Components/Button',
  component: Button,
  parameters: {
    layout: 'centered',
  },
  tags: ['autodocs'],
  argTypes: {
    variant: {
      control: { type: 'select' },
      options: ['primary', 'secondary'],
    },
    size: {
      control: { type: 'select' },
      options: ['sm', 'md', 'lg'],
    },
  },
};

export default meta;
type Story = StoryObj<typeof meta>;

export const Primary: Story = {
  args: {
    variant: 'primary',
    children: 'Primary Button',
  },
};

export const Secondary: Story = {
  args: {
    variant: 'secondary',
    children: 'Secondary Button',
  },
};

export const Small: Story = {
  args: {
    size: 'sm',
    children: 'Small Button',
  },
};

export const Large: Story = {
  args: {
    size: 'lg',
    children: 'Large Button',
  },
};

ブランド統一を実現する Design Tokens の活用パターン

Design Tokens を効果的に活用してブランド統一を実現するための実践的なパターンを紹介します。

1. セマンティックカラーの活用

ブランドの意味を明確に伝えるセマンティックカラーを定義します:

json// tokens/semantic-colors.json
{
  "color": {
    "semantic": {
      "success": {
        "value": "#10b981",
        "type": "color",
        "description": "成功状態を示す色"
      },
      "error": {
        "value": "#ef4444",
        "type": "color",
        "description": "エラー状態を示す色"
      },
      "warning": {
        "value": "#f59e0b",
        "type": "color",
        "description": "警告状態を示す色"
      },
      "info": {
        "value": "#3b82f6",
        "type": "color",
        "description": "情報状態を示す色"
      }
    }
  }
}

2. レスポンシブデザインのためのトークン

異なる画面サイズに対応するためのトークンを定義します:

json// tokens/breakpoints.json
{
  "breakpoint": {
    "mobile": {
      "value": "320px",
      "type": "size"
    },
    "tablet": {
      "value": "768px",
      "type": "size"
    },
    "desktop": {
      "value": "1024px",
      "type": "size"
    },
    "wide": {
      "value": "1440px",
      "type": "size"
    }
  }
}

3. アニメーションとトランジションの統一

ブランドの動きを統一するためのアニメーショントークンを定義します:

json// tokens/animation.json
{
  "animation": {
    "duration": {
      "fast": {
        "value": "150ms",
        "type": "time"
      },
      "normal": {
        "value": "250ms",
        "type": "time"
      },
      "slow": {
        "value": "350ms",
        "type": "time"
      }
    },
    "easing": {
      "ease-in": {
        "value": "cubic-bezier(0.4, 0, 1, 1)",
        "type": "cubicBezier"
      },
      "ease-out": {
        "value": "cubic-bezier(0, 0, 0.2, 1)",
        "type": "cubicBezier"
      },
      "ease-in-out": {
        "value": "cubic-bezier(0.4, 0, 0.2, 1)",
        "type": "cubicBezier"
      }
    }
  }
}

4. ダークモード対応

ダークモードに対応するためのトークンを定義します:

json// tokens/dark-mode.json
{
  "color": {
    "background": {
      "primary": {
        "value": "#ffffff",
        "type": "color",
        "darkValue": "#1a1a1a"
      },
      "secondary": {
        "value": "#f8fafc",
        "type": "color",
        "darkValue": "#2a2a2a"
      }
    },
    "text": {
      "primary": {
        "value": "#1e293b",
        "type": "color",
        "darkValue": "#f1f5f9"
      },
      "secondary": {
        "value": "#64748b",
        "type": "color",
        "darkValue": "#94a3b8"
      }
    }
  }
}

5. アクセシビリティ対応

アクセシビリティを考慮したトークンを定義します:

json// tokens/accessibility.json
{
  "color": {
    "contrast": {
      "high": {
        "value": "4.5:1",
        "type": "ratio"
      },
      "medium": {
        "value": "3:1",
        "type": "ratio"
      }
    }
  },
  "spacing": {
    "touch": {
      "minimum": {
        "value": "44px",
        "type": "size",
        "description": "タッチターゲットの最小サイズ"
      }
    }
  }
}

チーム開発での Design Tokens 運用ベストプラクティス

チーム全体で Design Tokens を効果的に運用するためのベストプラクティスを紹介します。

1. 命名規則の統一

チーム全体で一貫した命名規則を使用します:

javascript// 推奨される命名規則
const namingConventions = {
  // セマンティック命名
  semantic: {
    color: 'color-semantic-success',
    spacing: 'spacing-component-button-padding',
    typography: 'typography-heading-h1',
  },

  // 階層構造
  hierarchy: {
    global: 'global-color-brand-primary',
    component: 'component-button-primary-background',
    state: 'state-hover-background',
  },
};

2. バージョン管理と変更履歴

Design Tokens の変更を適切に管理します:

json// tokens/version.json
{
  "version": "1.2.0",
  "changes": [
    {
      "version": "1.2.0",
      "date": "2024-01-15",
      "changes": [
        "Added new semantic color tokens for improved accessibility",
        "Updated primary brand color to match new brand guidelines",
        "Deprecated old spacing tokens in favor of new scale"
      ]
    }
  ]
}

3. 自動化と CI/CD パイプライン

Design Tokens の更新を自動化します:

yaml# .github/workflows/design-tokens.yml
name: Design Tokens CI

on:
  push:
    paths:
      - 'tokens/**/*.json'
  pull_request:
    paths:
      - 'tokens/**/*.json'

jobs:
  build-tokens:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3

      - name: Setup Node.js
        uses: actions/setup-node@v3
        with:
          node-version: '18'
          cache: 'yarn'

      - name: Install dependencies
        run: yarn install

      - name: Build design tokens
        run: yarn build:tokens

      - name: Run tests
        run: yarn test:tokens

      - name: Create pull request
        if: github.event_name == 'push'
        uses: peter-evans/create-pull-request@v4
        with:
          title: 'Update design tokens'
          body: 'Automatically generated from design tokens changes'
          branch: update-design-tokens

4. ドキュメント化とガイドライン

Design Tokens の使用方法を明確にドキュメント化します:

markdown# Design Tokens ガイドライン

# 使用方法

## 色の使用

- ブランドカラー: `var(--color-brand-primary-500)`
- セマンティックカラー: `var(--color-semantic-success)`
- 背景色: `var(--color-background-primary)`

## スペーシングの使用

- コンポーネント内: `var(--spacing-sm)`
- セクション間: `var(--spacing-lg)`
- ページ全体: `var(--spacing-xl)`

## タイポグラフィの使用

- 見出し: `var(--font-size-xl)`
- 本文: `var(--font-size-base)`
- キャプション: `var(--font-size-sm)`

# 禁止事項

- ハードコードされた値の使用
- 既存トークンと異なる命名規則の使用
- セマンティックでない色の直接指定

5. 品質保証とテスト

Design Tokens の品質を保証するためのテストを実装します:

javascript// tests/design-tokens.test.js
const fs = require('fs');
const path = require('path');

describe('Design Tokens', () => {
  test('should have valid JSON structure', () => {
    const tokensPath = path.join(__dirname, '../tokens');
    const tokenFiles = getAllJsonFiles(tokensPath);

    tokenFiles.forEach((file) => {
      const content = fs.readFileSync(file, 'utf8');
      expect(() => JSON.parse(content)).not.toThrow();
    });
  });

  test('should have consistent naming conventions', () => {
    const tokens = loadAllTokens();

    Object.keys(tokens).forEach((category) => {
      Object.keys(tokens[category]).forEach((token) => {
        expect(token).toMatch(/^[a-z][a-z0-9-]*$/);
      });
    });
  });

  test('should have valid color values', () => {
    const tokens = loadAllTokens();

    if (tokens.color) {
      Object.values(tokens.color).forEach((colorToken) => {
        if (colorToken.value) {
          expect(colorToken.value).toMatch(
            /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/
          );
        }
      });
    }
  });
});

function getAllJsonFiles(dir) {
  // 実装省略
}

function loadAllTokens() {
  // 実装省略
}

6. よくあるエラーと対処法

Design Tokens の実装でよく発生するエラーとその対処法を紹介します:

エラー 1: CSS 変数が未定義

css/* エラー: CSS 変数が未定義 */
.button {
  background-color: var(
    --color-primary-500
  ); /* 未定義の変数 */
}

対処法: Design Tokens のビルドプロセスを確認し、CSS ファイルが正しく生成されているかチェックします。

bash# ビルドプロセスの確認
yarn build:tokens
cat src/styles/tokens.css

エラー 2: 型の不一致

typescript// エラー: 型の不一致
interface ButtonProps {
  variant: 'primary' | 'secondary' | 'invalid'; // 存在しないバリアント
}

対処法: TypeScript の型定義を Design Tokens から自動生成します。

javascript// scripts/generate-types.js
const StyleDictionary = require('style-dictionary');

const styleDictionary = StyleDictionary.extend({
  source: ['tokens/**/*.json'],
  platforms: {
    typescript: {
      transformGroup: 'typescript',
      buildPath: 'src/types/',
      files: [
        {
          destination: 'design-tokens.ts',
          format: 'typescript/module',
        },
      ],
    },
  },
});

エラー 3: パフォーマンスの問題

css/* 問題: 大量の CSS 変数によるパフォーマンス低下 */
:root {
  /* 数百個の CSS 変数 */
}

対処法: 必要なトークンのみをインポートする仕組みを構築します。

javascript// utils/token-loader.js
export function loadTokens(categories = []) {
  const tokens = {};

  categories.forEach((category) => {
    const categoryTokens = require(`../tokens/${category}.json`);
    Object.assign(tokens, categoryTokens);
  });

  return tokens;
}

まとめ

Storybook と Design Tokens を組み合わせることで、ブランド統一を実現し、開発効率を大幅に向上させることができます。

この記事で紹介した手法を実践することで、以下のような効果が期待できます:

  • 一貫性の向上: すべてのコンポーネントで統一されたデザインが実現されます
  • 開発効率の向上: デザインの変更が容易になり、開発速度が向上します
  • チーム間の連携強化: デザイナーと開発者のコミュニケーションが円滑になります
  • 品質の向上: デザインの一貫性により、ユーザー体験が向上します
  • 保守性の向上: デザインの変更が一箇所で管理できるようになります

Design Tokens の導入は、最初は少し手間がかかるかもしれませんが、長期的に見れば大きな投資対効果を得ることができます。チーム全体で一貫したデザインシステムを構築し、ブランドの価値を最大化していきましょう。

関連リンク