T-CREATOR

Storybook でダークモード対応を爆速実装

Storybook でダークモード対応を爆速実装

現代の Web 開発において、ダークモード対応はもはや必須の機能となっています。ユーザーの目に優しく、長時間の作業でも疲れにくいダークモードは、UX 向上に大きく貢献する要素です。

しかし、Storybook でダークモードを実装しようとすると、設定が複雑で時間がかかってしまうことが多いのではないでしょうか。本記事では、最小限の手順で Storybook にダークモード対応を実装する方法をご紹介します。

実際のプロジェクトで使える実践的なコードと、よくあるエラーとその解決方法も含めて、あなたの開発効率を劇的に向上させる内容をお届けします。

Storybook とは

Storybook は、UI コンポーネントを独立して開発・テスト・ドキュメント化できるオープンソースのツールです。React、Vue、Angular など、主要なフレームワークに対応しており、コンポーネントライブラリの構築に欠かせない存在となっています。

Storybook の最大の魅力は、コンポーネントを実際のアプリケーションから切り離して開発できることです。これにより、様々な状態やプロパティでのコンポーネントの動作を確認でき、品質の高い UI を効率的に構築できます。

ダークモード対応の必要性

ダークモード対応が重要な理由は、単にトレンドだからではありません。実際のユーザーニーズに基づいています。

ユーザー体験の向上

  • 夜間や暗い環境での使用時の目の疲労軽減
  • バッテリー消費の削減(OLED ディスプレイの場合)
  • アクセシビリティの向上

開発効率の向上

  • デザインシステムの一貫性確保
  • コンポーネントの視認性テスト
  • ブランドカラーの適切な適用確認

Storybook でダークモードを実装することで、これらの要素を開発段階から考慮した高品質なコンポーネントを作成できます。

事前準備

Storybook でダークモード対応を実装する前に、いくつかの前提条件を確認しましょう。

必要な環境

  • Node.js 16.0 以上
  • Yarn パッケージマネージャー
  • 既存の Storybook プロジェクト

確認事項

  • Storybook のバージョン(6.0 以上推奨)
  • 使用しているフレームワーク(React、Vue、Angular 等)
  • 既存のテーマ設定の有無

まず、現在の Storybook のバージョンを確認してみましょう。

bashyarn storybook --version

もし Storybook がインストールされていない場合は、以下のコマンドで新規プロジェクトを作成できます。

bashnpx storybook@latest init

実装手順

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

ダークモード対応に必要なパッケージをインストールします。最小限の依存関係で実装するため、必要最小限のパッケージのみを使用します。

bashyarn add -D @storybook/addon-themes

このパッケージは、Storybook でテーマ切り替えを簡単に実装するための公式アドオンです。CSS 変数やテーマオブジェクトを活用して、スムーズなテーマ切り替えを実現します。

インストール後、以下のエラーが発生した場合の対処法をご紹介します。

よくあるエラー 1: パッケージの競合

goerror An unexpected error occurred: "ENOENT: no such file or directory, open 'package.json'"

このエラーは、プロジェクトのルートディレクトリにいない場合に発生します。正しいディレクトリに移動してから実行してください。

bashcd /path/to/your/project
yarn add -D @storybook/addon-themes

よくあるエラー 2: バージョンの不整合

kotlinerror @storybook/addon-themes@7.x.x is not compatible with storybook@6.x.x

この場合は、Storybook のバージョンをアップグレードする必要があります。

bashyarn upgrade storybook@latest

2. Storybook 設定ファイルの作成

Storybook の設定ファイルを作成して、テーマアドオンを有効にします。

まず、.storybook​/​main.js(または.ts)ファイルを確認・編集します。

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-themes', // 新しく追加
  ],
  framework: {
    name: '@storybook/react-webpack5',
    options: {},
  },
  docs: {
    autodocs: 'tag',
  },
};

次に、.storybook​/​preview.js(または.ts)ファイルを作成・編集します。

javascript// .storybook/preview.js
import { withThemeByClassName } from '@storybook/addon-themes';

export const decorators = [
  withThemeByClassName({
    themes: {
      light: 'light',
      dark: 'dark',
    },
    defaultTheme: 'light',
  }),
];

export const parameters = {
  actions: { argTypesRegex: '^on[A-Z].*' },
  controls: {
    matchers: {
      color: /(background|color)$/i,
      date: /Date$/,
    },
  },
};

この設定により、Storybook のツールバーにテーマ切り替えボタンが表示されるようになります。

3. テーマ切り替えコンポーネントの実装

実際のアプリケーションで使用するテーマ切り替えコンポーネントを実装します。このコンポーネントは、Storybook でのテストにも使用できます。

typescript// src/components/ThemeToggle.tsx
import React, { useState, useEffect } from 'react';

interface ThemeToggleProps {
  className?: string;
}

export const ThemeToggle: React.FC<ThemeToggleProps> = ({
  className = '',
}) => {
  const [isDark, setIsDark] = useState(false);

  // 初期テーマの設定
  useEffect(() => {
    const savedTheme = localStorage.getItem('theme');
    const prefersDark = window.matchMedia(
      '(prefers-color-scheme: dark)'
    ).matches;

    const theme =
      savedTheme || (prefersDark ? 'dark' : 'light');
    setIsDark(theme === 'dark');
    document.documentElement.classList.toggle(
      'dark',
      theme === 'dark'
    );
  }, []);

  // テーマ切り替え処理
  const toggleTheme = () => {
    const newTheme = !isDark;
    setIsDark(newTheme);
    document.documentElement.classList.toggle(
      'dark',
      newTheme
    );
    localStorage.setItem(
      'theme',
      newTheme ? 'dark' : 'light'
    );
  };

  return (
    <button
      onClick={toggleTheme}
      className={`theme-toggle ${className}`}
      aria-label={
        isDark
          ? 'ライトモードに切り替え'
          : 'ダークモードに切り替え'
      }
    >
      {isDark ? '☀️' : '🌙'}
    </button>
  );
};

このコンポーネントの特徴は、システム設定の自動検出とローカルストレージでの設定保存です。ユーザーの好みを尊重しながら、一貫した体験を提供します。

4. グローバルデコレーターの設定

すべてのストーリーでダークモードが適用されるように、グローバルデコレーターを設定します。

javascript// .storybook/preview.js(更新版)
import { withThemeByClassName } from '@storybook/addon-themes';
import '../src/styles/globals.css'; // グローバルスタイルのインポート

export const decorators = [
  withThemeByClassName({
    themes: {
      light: '',
      dark: 'dark',
    },
    defaultTheme: 'light',
  }),
];

export const parameters = {
  actions: { argTypesRegex: '^on[A-Z].*' },
  controls: {
    matchers: {
      color: /(background|color)$/i,
      date: /Date$/,
    },
  },
  // ダークモードの背景色設定
  backgrounds: {
    default: 'light',
    values: [
      {
        name: 'light',
        value: '#ffffff',
      },
      {
        name: 'dark',
        value: '#1a1a1a',
      },
    ],
  },
};

この設定により、テーマ切り替えと背景色の変更が連動して動作するようになります。

5. カスタムテーマの定義

CSS 変数を使用してカスタムテーマを定義します。これにより、一貫したデザインシステムを構築できます。

css/* src/styles/globals.css */
:root {
  /* ライトモードのカラー */
  --color-primary: #3b82f6;
  --color-secondary: #64748b;
  --color-background: #ffffff;
  --color-surface: #f8fafc;
  --color-text: #1e293b;
  --color-text-secondary: #64748b;
  --color-border: #e2e8f0;
  --color-shadow: rgba(0, 0, 0, 0.1);
}

/* ダークモードのカラー */
.dark {
  --color-primary: #60a5fa;
  --color-secondary: #94a3b8;
  --color-background: #0f172a;
  --color-surface: #1e293b;
  --color-text: #f1f5f9;
  --color-text-secondary: #cbd5e1;
  --color-border: #334155;
  --color-shadow: rgba(0, 0, 0, 0.3);
}

/* 基本スタイル */
body {
  background-color: var(--color-background);
  color: var(--color-text);
  transition: background-color 0.3s ease, color 0.3s ease;
}

/* テーマ切り替えボタンのスタイル */
.theme-toggle {
  padding: 8px 12px;
  border: 1px solid var(--color-border);
  border-radius: 6px;
  background: var(--color-surface);
  color: var(--color-text);
  cursor: pointer;
  transition: all 0.3s ease;
}

.theme-toggle:hover {
  background: var(--color-primary);
  color: white;
}

この CSS 変数システムにより、テーマ切り替えがスムーズに行われ、メンテナンス性も向上します。

動作確認

実装が完了したら、実際に動作を確認してみましょう。

bashyarn storybook

Storybook が起動したら、以下の点を確認してください:

  1. ツールバーのテーマ切り替えボタン

    • 右上のツールバーに「Globals」ボタンがあることを確認
    • クリックして「Theme」セクションでライト/ダークの切り替えができることを確認
  2. コンポーネントの表示

    • 各ストーリーでテーマ切り替えが正常に動作することを確認
    • 色や背景が適切に変更されることを確認
  3. ThemeToggle コンポーネントのテスト

    • 作成した ThemeToggle コンポーネントのストーリーを作成してテスト
typescript// src/components/ThemeToggle.stories.tsx
import type { Meta, StoryObj } from '@storybook/react';
import { ThemeToggle } from './ThemeToggle';

const meta: Meta<typeof ThemeToggle> = {
  title: 'Components/ThemeToggle',
  component: ThemeToggle,
  parameters: {
    layout: 'centered',
  },
  tags: ['autodocs'],
};

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

export const Default: Story = {
  args: {},
};

export const WithCustomClass: Story = {
  args: {
    className: 'custom-theme-toggle',
  },
};

よくある問題と解決方法

問題 1: テーマ切り替えが反映されない

makefileエラー: CSS変数が適用されていない

解決方法:CSS ファイルが正しくインポートされているか確認してください。

問題 2: ストーリーでテーマが切り替わらない

makefileエラー: デコレーターが正しく動作していない

解決方法:.storybook​/​preview.jsの設定を再確認し、Storybook を再起動してください。

カスタマイズのポイント

基本的な実装が完了したら、プロジェクトの要件に合わせてカスタマイズしましょう。

1. ブランドカラーの適用

css/* ブランドに合わせたカスタムカラー */
:root {
  --color-brand-primary: #your-brand-color;
  --color-brand-secondary: #your-secondary-color;
}

.dark {
  --color-brand-primary: #your-dark-brand-color;
  --color-brand-secondary: #your-dark-secondary-color;
}

2. アニメーションの追加

css/* スムーズなテーマ切り替えアニメーション */
* {
  transition: background-color 0.3s ease, color 0.3s ease,
    border-color 0.3s ease;
}

3. システム設定との連携

typescript// システム設定の変更を監視
useEffect(() => {
  const mediaQuery = window.matchMedia(
    '(prefers-color-scheme: dark)'
  );
  const handleChange = (e: MediaQueryListEvent) => {
    const newTheme = e.matches ? 'dark' : 'light';
    setIsDark(e.matches);
    document.documentElement.classList.toggle(
      'dark',
      e.matches
    );
  };

  mediaQuery.addEventListener('change', handleChange);
  return () =>
    mediaQuery.removeEventListener('change', handleChange);
}, []);

これらのカスタマイズにより、より洗練されたダークモード体験を提供できます。

まとめ

Storybook でダークモード対応を実装することで、開発段階からユーザー体験を考慮した高品質なコンポーネントを作成できるようになりました。

今回ご紹介した実装方法のポイントをまとめると:

  • 最小限の設定: @storybook​/​addon-themesを使用して、複雑な設定なしでダークモード対応を実現
  • ユーザビリティ重視: システム設定の自動検出とローカルストレージでの設定保存
  • 保守性の向上: CSS 変数を使用した一貫したテーマシステム
  • 拡張性の確保: プロジェクトの要件に合わせた柔軟なカスタマイズが可能

この実装により、開発チーム全体でダークモード対応の重要性を理解し、ユーザーに愛されるアプリケーションの開発が加速することでしょう。

実際のプロジェクトでこの実装を活用していただき、ユーザー体験の向上に貢献していただければ幸いです。

関連リンク