T-CREATOR

Storybook Decorators/Parameters 辞典:背景・テーマ・グローバル設定の定石

Storybook Decorators/Parameters 辞典:背景・テーマ・グローバル設定の定石

Storybook でコンポーネントを開発していると、「毎回同じ背景色を設定するのが面倒」「テーマプロバイダーを全ストーリーに追加したい」といった共通設定の課題に直面します。こうした繰り返し作業を解消し、より快適な開発体験を実現するのが DecoratorsParameters です。

本記事では、Storybook の Decorators と Parameters の基本から実践的な活用方法まで、背景・テーマ・グローバル設定の定石パターンを網羅的に解説していきます。Storybook v7/v8 に対応した TypeScript の実装例とともに、すぐに使える設定パターンをご紹介しますので、ぜひ最後までお読みください。

Decorators/Parameters 早見表

まず、Decorators と Parameters の基本的な違いと使い分けを一覧で確認しましょう。

基本比較表

#項目DecoratorsParameters
1役割コンポーネントをラップするアドオンやツールの動作を制御する
2実装形式React コンポーネント(関数)オブジェクト形式のメタデータ
3影響範囲DOM 構造に直接影響Storybook UI の表示や動作に影響
4記述場所decorators プロパティparameters プロパティ
5実行タイミングコンポーネントレンダリング時設定読み込み時

主な使用例一覧

#用途使用する機能設定場所コード例
1テーマプロバイダーの適用Decorators.storybook​/​preview.tsx<ThemeProvider>でラップ
2ルーターの適用Decorators.storybook​/​preview.tsx<MemoryRouter>でラップ
3グローバルスタイルの適用Decorators.storybook​/​preview.tsx<GlobalStyles ​/​>を挿入
4国際化プロバイダーの適用Decorators.storybook​/​preview.tsx<I18nextProvider>でラップ
5背景色の設定Parameters.storybook​/​preview.tsbackgrounds: { default: 'light' }
6レイアウトモードの設定Parameters.storybook​/​preview.tslayout: 'centered'
7ビューポートの設定Parameters.storybook​/​preview.tsviewport: { defaultViewport: 'mobile' }
8Actions アドオンの設定Parameters.storybook​/​preview.tsactions: { argTypesRegex: '^on[A-Z].*' }

適用レベル早見表

#適用レベル設定ファイル適用範囲優先度
1グローバル.storybook​/​preview.ts(x)プロジェクト全体の全ストーリー
2コンポーネントComponentName.stories.tsxmeta同じコンポーネントの全ストーリー
3ストーリー個別のStoryオブジェクト特定のストーリーのみ

上位レベル(ストーリー)の設定が下位レベル(グローバル)の設定を上書きします。

背景

Storybook における共通設定の必要性

Storybook は UI コンポーネントを独立した環境で開発・テストできる強力なツールです。しかし、プロジェクトが成長するにつれて、以下のような共通設定を各ストーリーで繰り返し記述する必要が出てきます。

プロジェクト全体で必要となる共通設定の例:

#設定項目内容
1テーマプロバイダーアプリケーション全体のテーマ(ライト/ダーク)を提供
2国際化(i18n)多言語対応のためのプロバイダー設定
3背景色・レイアウトコンポーネントを見やすく表示するための装飾
4グローバルスタイルCSS リセットやフォント設定
5ルーター設定React Router などのナビゲーション機能

これらを毎回のストーリーファイルに記述すると、コードの重複が増え、メンテナンス性が著しく低下してしまうでしょう。

Decorators と Parameters の役割

Storybook はこの課題を解決するために、Decorators(デコレーター)Parameters(パラメーター) という 2 つの機能を提供しています。

以下の図で、Storybook におけるストーリーのレンダリングフローと、Decorators・Parameters がどのように作用するかを確認しましょう。

mermaidflowchart TB
  start["Storybook 起動"] --> global["グローバル設定<br/>(preview.js/ts)"]
  global --> story["個別ストーリー<br/>定義"]

  global -->|適用| decorators["Decorators<br/>(ラッパー機能)"]
  global -->|適用| params["Parameters<br/>(メタデータ)"]

  decorators --> wrap["コンポーネントを<br/>ラップ"]
  params --> addon["アドオンの<br/>動作制御"]

  wrap --> render["最終レンダリング"]
  addon --> render
  story --> render

  render --> display["ブラウザ表示"]

図で理解できる要点:

  • グローバル設定が全ストーリーに適用される起点となる
  • Decorators はコンポーネントを物理的にラップして機能を追加
  • Parameters はアドオンやツールバーの動作を制御する

この仕組みにより、共通設定を一箇所で管理し、全ストーリーに適用できるようになります。

課題

各ストーリーでの重複コード問題

Decorators と Parameters を使わない場合、各ストーリーファイルで同じようなコードを繰り返し記述しなければなりません。

以下は、テーマプロバイダーを各ストーリーで個別に設定する例です。

typescript// Button.stories.tsx(悪い例)
import type { Meta, StoryObj } from '@storybook/react';
import { ThemeProvider } from 'styled-components';
import { Button } from './Button';
import { lightTheme } from '../theme';

各ストーリーごとにテーマプロバイダーを手動でラップする必要があります。

typescriptconst meta: Meta<typeof Button> = {
  title: 'Components/Button',
  component: Button,
};

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

ストーリーの定義でも、毎回同じラッパーコードを記述することになります。

typescriptexport const Primary: Story = {
  render: (args) => (
    <ThemeProvider theme={lightTheme}>
      <Button {...args} />
    </ThemeProvider>
  ),
  args: {
    label: 'Primary Button',
    variant: 'primary',
  },
};

このアプローチの問題点を整理してみましょう。

重複コードによる課題:

#課題影響
1コードの重複全ストーリーファイルで同じラッパーコードを記述
2保守性の低下テーマ変更時に全ファイルを修正する必要がある
3記述ミスコピー&ペーストによる設定漏れやタイポのリスク
4可読性の悪化本質的なストーリー定義がラッパーコードに埋もれる

テーマやレイアウトの統一管理の難しさ

さらに、複数の設定を組み合わせる場合、問題はより深刻になります。

テーマ、国際化、ルーターを全て設定する場合の例を見てみましょう。

typescript// Card.stories.tsx(複数プロバイダーを使う悪い例)
export const Default: Story = {
  render: (args) => (
    <ThemeProvider theme={lightTheme}>
      <I18nProvider locale='ja'>
        <MemoryRouter>
          <Card {...args} />
        </MemoryRouter>
      </I18nProvider>
    </ThemeProvider>
  ),
};

ネストが深くなり、コードの可読性が著しく低下してしまいます。また、新しいストーリーを追加するたびに、このネスト構造を正確に再現する必要があります。

以下の図で、課題の構造を視覚的に理解できます。

mermaidflowchart LR
  story1["Button<br/>ストーリー"] --> dup1["テーマ<br/>プロバイダー"]
  story2["Card<br/>ストーリー"] --> dup2["テーマ<br/>プロバイダー"]
  story3["Input<br/>ストーリー"] --> dup3["テーマ<br/>プロバイダー"]

  dup1 -.重複.-> issue["コード重複<br/>保守困難"]
  dup2 -.重複.-> issue
  dup3 -.重複.-> issue

  issue --> maintain["全ファイル<br/>修正が必要"]

課題の要点:

  • 同じ設定が複数ファイルに散在
  • 一括変更が困難で保守コストが高い
  • 設定漏れや不整合が発生しやすい

これらの課題を根本的に解決するのが、次のセクションで解説する Decorators と Parameters の活用です。

解決策

Decorators(デコレーター)の仕組み

Decorators は、ストーリーをレンダリングする際に、コンポーネントをラップする関数です。テーマプロバイダーやコンテキスト、スタイルなど、コンポーネントの外側に追加したい要素を一箇所で定義できます。

Decorators の基本的な型定義を確認しましょう。

typescript// Decorator の型定義
import type { Decorator } from '@storybook/react';

// Story は Decorator によってラップされる
// context にはストーリーの情報が含まれる
type DecoratorFunction = (
  Story: React.ComponentType,
  context: StoryContext
) => JSX.Element;

Decorators は以下の 3 つのレベルで適用できます。

Decorators の適用レベル:

#レベル設定場所適用範囲
1グローバル.storybook​/​preview.ts全ストーリー
2コンポーネント*.stories.tsxmeta同じコンポーネントの全ストーリー
3ストーリー個別の Story オブジェクト特定のストーリーのみ

最もシンプルな Decorator の実装例を見てみましょう。

typescript// .storybook/preview.ts
import type { Preview, Decorator } from '@storybook/react';
import { ThemeProvider } from 'styled-components';
import { lightTheme } from '../src/theme';

グローバル Decorator を定義します。この Decorator は全ストーリーに自動適用されます。

typescript// Story コンポーネントを受け取り、ThemeProvider でラップして返す
const withTheme: Decorator = (Story) => {
  return (
    <ThemeProvider theme={lightTheme}>
      <Story />
    </ThemeProvider>
  );
};

Preview 設定で Decorator を登録します。

typescriptconst preview: Preview = {
  decorators: [withTheme],
};

export default preview;

この設定により、全てのストーリーが自動的に ThemeProvider でラップされるようになります。

Parameters(パラメーター)の仕組み

Parameters は、ストーリーやアドオンの動作を制御するメタデータです。背景色、ビューポートサイズ、アクションの設定など、見た目や振る舞いの調整に使用します。

Parameters も Decorators と同様に、3 つのレベルで設定可能です。

typescript// Parameters の基本構造
interface Parameters {
  backgrounds?: {
    default?: string;
    values?: Array<{ name: string; value: string }>;
  };
  viewport?: {
    defaultViewport?: string;
  };
  layout?: 'centered' | 'fullscreen' | 'padded';
  // その他、アドオン固有の設定
}

グローバル Parameters の設定例を見てみましょう。

typescript// .storybook/preview.ts
import type { Preview } from '@storybook/react';

const preview: Preview = {
  parameters: {
    // 背景色の選択肢を定義
    backgrounds: {
      default: 'light',
      values: [
        { name: 'light', value: '#ffffff' },
        { name: 'dark', value: '#333333' },
      ],
    },
  },
};

レイアウトモードとビューポート設定も追加できます。

typescriptconst preview: Preview = {
  parameters: {
    // コンポーネントを中央配置
    layout: 'centered',

    // デフォルトのビューポート
    viewport: {
      defaultViewport: 'responsive',
    },
  },
};

export default preview;

Decorators と Parameters の使い分け

両者の違いを理解することで、適切に使い分けられるようになります。

機能比較表:

#項目DecoratorsParameters
1目的コンポーネントのラップアドオン・ツールの制御
2実装React コンポーネントオブジェクト形式のメタデータ
3用途例テーマ、ルーター、コンテキスト背景色、ビューポート、レイアウト
4影響範囲DOM 構造に影響Storybook UI に影響
5実行タイミングレンダリング時設定読み込み時

以下の図で、両者の役割分担を確認できます。

mermaidflowchart TB
  config["Storybook 設定<br/>(preview.ts)"]

  config --> deco["Decorators"]
  config --> param["Parameters"]

  deco --> wrap1["Provider のラップ"]
  deco --> wrap2["レイアウトコンポーネント"]
  deco --> wrap3["コンテキスト提供"]

  param --> ui1["背景色の制御"]
  param --> ui2["ビューポート設定"]
  param --> ui3["アドオンの動作"]

  wrap1 --> result["コンポーネント<br/>レンダリング"]
  wrap2 --> result
  wrap3 --> result

  ui1 --> canvas["Storybook UI"]
  ui2 --> canvas
  ui3 --> canvas

使い分けの指針:

  • Decorators を使う場合: React のプロバイダーやラッパーコンポーネントが必要なとき
  • Parameters を使う場合: Storybook の UI やアドオンの動作を制御したいとき
  • 両方を組み合わせる: テーマ切り替え(Decorator でプロバイダー、Parameters でツールバー制御)

具体例

背景色とレイアウトの設定

まず、最も基本的な背景色とレイアウトの設定から始めましょう。これは Parameters を使って実現します。

.storybook​/​preview.ts でグローバル背景色を設定します。

typescript// .storybook/preview.ts
import type { Preview } from '@storybook/react';

const preview: Preview = {
  parameters: {
    // 背景色のプリセットを定義
    backgrounds: {
      default: 'light',
      values: [
        { name: 'light', value: '#f8f9fa' },
        { name: 'dark', value: '#1a1a1a' },
        { name: 'brand', value: '#0066cc' },
      ],
    },
  },
};

レイアウトモードも設定できます。

typescriptconst preview: Preview = {
  parameters: {
    // コンポーネントを中央に配置
    layout: 'centered',

    // actions アドオンの設定
    actions: { argTypesRegex: '^on[A-Z].*' },
  },
};

export default preview;

特定のストーリーで背景色を上書きする場合の例です。

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

const meta: Meta<typeof Button> = {
  title: 'Components/Button',
  component: Button,
};

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

このストーリーだけ暗い背景色で表示します。

typescriptexport const OnDarkBackground: Story = {
  args: {
    label: 'Dark Background Button',
    variant: 'primary',
  },
  parameters: {
    backgrounds: {
      default: 'dark',
    },
  },
};

テーマプロバイダーの適用

次に、実務で最も頻繁に使われるテーマプロバイダーの設定を見ていきます。

styled-components を使ったテーマ定義の例です。

typescript// src/theme/index.ts
export const lightTheme = {
  colors: {
    primary: '#0066cc',
    background: '#ffffff',
    text: '#333333',
  },
  spacing: {
    small: '8px',
    medium: '16px',
    large: '24px',
  },
};

ダークテーマも定義します。

typescriptexport const darkTheme = {
  colors: {
    primary: '#4d94ff',
    background: '#1a1a1a',
    text: '#f8f9fa',
  },
  spacing: {
    small: '8px',
    medium: '16px',
    large: '24px',
  },
};

グローバル Decorator でテーマプロバイダーを適用します。

typescript// .storybook/preview.tsx
import type { Preview, Decorator } from '@storybook/react';
import { ThemeProvider } from 'styled-components';
import { lightTheme, darkTheme } from '../src/theme';

context から背景色の設定を取得し、対応するテーマを適用します。

typescriptconst withTheme: Decorator = (Story, context) => {
  // Parameters から背景色の設定を取得
  const background =
    context.parameters.backgrounds?.default || 'light';
  const theme =
    background === 'dark' ? darkTheme : lightTheme;

  return (
    <ThemeProvider theme={theme}>
      <Story />
    </ThemeProvider>
  );
};

Preview 設定で Decorator を登録します。

typescriptconst preview: Preview = {
  decorators: [withTheme],
  parameters: {
    backgrounds: {
      default: 'light',
      values: [
        { name: 'light', value: '#ffffff' },
        { name: 'dark', value: '#1a1a1a' },
      ],
    },
  },
};

export default preview;

この設定により、Storybook のツールバーで背景色を切り替えると、自動的にテーマも切り替わります。

グローバルスタイルの適用

CSS リセットやフォント設定などのグローバルスタイルも Decorator で適用できます。

styled-components の createGlobalStyle を使った例を見てみましょう。

typescript// src/styles/GlobalStyles.tsx
import { createGlobalStyle } from 'styled-components';

export const GlobalStyles = createGlobalStyle`
  * {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
  }

フォントやベーススタイルを定義します。

typescript  body {
    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI',
      'Roboto', 'Oxygen', 'Ubuntu', sans-serif;
    font-size: 16px;
    line-height: 1.6;
    color: ${props => props.theme.colors.text};
    background-color: ${props => props.theme.colors.background};
  }
`;

Decorator でグローバルスタイルを適用します。

typescript// .storybook/preview.tsx
import { GlobalStyles } from '../src/styles/GlobalStyles';

const withGlobalStyles: Decorator = (Story) => {
  return (
    <>
      <GlobalStyles />
      <Story />
    </>
  );
};

複数の Decorator を組み合わせて適用します。

typescriptconst preview: Preview = {
  // 配列の最後の Decorator が最も内側に適用される
  decorators: [withGlobalStyles, withTheme],
};

export default preview;

複数プロバイダーの組み合わせ

実際のプロジェクトでは、複数のプロバイダーを組み合わせる必要があります。React Context、ルーター、国際化などを統合する例を見ていきましょう。

各プロバイダーを個別に定義します。

typescript// .storybook/decorators.tsx
import type { Decorator } from '@storybook/react';
import { MemoryRouter } from 'react-router-dom';
import {
  QueryClient,
  QueryClientProvider,
} from '@tanstack/react-query';
import { I18nextProvider } from 'react-i18next';
import i18n from '../src/i18n';

ルーター用の Decorator を定義します。

typescriptexport const withRouter: Decorator = (Story) => {
  return (
    <MemoryRouter initialEntries={['/']}>
      <Story />
    </MemoryRouter>
  );
};

React Query 用の Decorator を定義します。クエリクライアントは各ストーリーで新規作成することで、テスト間の干渉を防ぎます。

typescriptexport const withQueryClient: Decorator = (Story) => {
  const queryClient = new QueryClient({
    defaultOptions: {
      queries: { retry: false },
    },
  });

  return (
    <QueryClientProvider client={queryClient}>
      <Story />
    </QueryClientProvider>
  );
};

国際化用の Decorator を定義します。

typescriptexport const withI18n: Decorator = (Story) => {
  return (
    <I18nextProvider i18n={i18n}>
      <Story />
    </I18nextProvider>
  );
};

全ての Decorator を統合して適用します。

typescript// .storybook/preview.tsx
import type { Preview } from '@storybook/react';
import {
  withRouter,
  withQueryClient,
  withI18n,
} from './decorators';
import { withTheme, withGlobalStyles } from './decorators';

const preview: Preview = {
  decorators: [
    withGlobalStyles,
    withTheme,
    withRouter,
    withQueryClient,
    withI18n,
  ],
};

export default preview;

以下の図で、複数の Decorator が重なり合う構造を確認できます。

mermaidflowchart TB
  start["Story<br/>コンポーネント"] --> i18n["I18nextProvider"]
  i18n --> query["QueryClientProvider"]
  query --> router["MemoryRouter"]
  router --> theme["ThemeProvider"]
  theme --> global["GlobalStyles"]
  global --> render["最終的な<br/>DOM 出力"]

  style start fill:#e3f2fd
  style render fill:#c8e6c9

プロバイダー適用の要点:

  • 配列の順序が重要(最後が最も内側)
  • 各 Decorator は独立してテスト可能
  • 新しいプロバイダーの追加が容易

ビューポートとレスポンシブ設定

モバイルファーストな開発では、複数のビューポートでコンポーネントを確認する必要があります。

カスタムビューポートを定義します。

typescript// .storybook/preview.ts
import type { Preview } from '@storybook/react';

const preview: Preview = {
  parameters: {
    viewport: {
      viewports: {
        mobile: {
          name: 'Mobile',
          styles: { width: '375px', height: '667px' },
        },
        tablet: {
          name: 'Tablet',
          styles: { width: '768px', height: '1024px' },
        },
        desktop: {
          name: 'Desktop',
          styles: { width: '1280px', height: '800px' },
        },
      },
    },
  },
};

デフォルトのビューポートを指定します。

typescriptconst preview: Preview = {
  parameters: {
    viewport: {
      defaultViewport: 'desktop',
    },
  },
};

export default preview;

特定のストーリーでビューポートを指定する例です。

typescript// MobileMenu.stories.tsx
export const Mobile: Story = {
  parameters: {
    viewport: {
      defaultViewport: 'mobile',
    },
    layout: 'fullscreen',
  },
};

コンポーネント固有の Decorator 設定

全てのストーリーに適用するのではなく、特定のコンポーネント群にのみ Decorator を適用したい場合もあるでしょう。

コンポーネントレベルの Decorator を定義します。

typescript// Card.stories.tsx
import type { Meta, StoryObj } from '@storybook/react';
import { Card } from './Card';

const meta: Meta<typeof Card> = {
  title: 'Components/Card',
  component: Card,
  // このコンポーネントの全ストーリーに適用される Decorator
  decorators: [
    (Story) => (
      <div style={{ maxWidth: '400px', margin: '0 auto' }}>
        <Story />
      </div>
    ),
  ],
};

export default meta;

この設定により、Card コンポーネントの全ストーリーが自動的に幅制限されたコンテナ内に表示されます。

複数の Decorator を組み合わせることも可能です。

typescriptconst meta: Meta<typeof Card> = {
  title: 'Components/Card',
  component: Card,
  decorators: [
    // パディングを追加
    (Story) => (
      <div style={{ padding: '24px' }}>
        <Story />
      </div>
    ),
    // 背景グリッドを追加
    (Story) => (
      <div
        style={{
          backgroundImage:
            'linear-gradient(#f0f0f0 1px, transparent 1px), linear-gradient(90deg, #f0f0f0 1px, transparent 1px)',
          backgroundSize: '20px 20px',
        }}
      >
        <Story />
      </div>
    ),
  ],
};

アドオン連携の Parameters 活用

Storybook の強力なアドオンと連携するための Parameters 設定を見ていきましょう。

Actions アドオンの設定例です。

typescript// .storybook/preview.ts
const preview: Preview = {
  parameters: {
    actions: {
      // on で始まる props を自動的に action として記録
      argTypesRegex: '^on[A-Z].*',
    },
  },
};

Controls アドオンで特定の props を制御する設定です。

typescript// Button.stories.tsx
const meta: Meta<typeof Button> = {
  title: 'Components/Button',
  component: Button,
  parameters: {
    controls: {
      // size プロパティ以外を非表示
      include: ['size', 'variant', 'disabled'],
    },
  },
};

Docs アドオンのカスタマイズ設定例です。

typescriptconst meta: Meta<typeof Button> = {
  title: 'Components/Button',
  component: Button,
  parameters: {
    docs: {
      description: {
        component:
          'アプリケーション全体で使用できる汎用ボタンコンポーネントです。',
      },
    },
  },
};

まとめ

本記事では、Storybook の Decorators と Parameters を活用した背景・テーマ・グローバル設定の定石パターンを解説しました。

重要なポイント:

#項目要点
1Decorators の役割コンポーネントをラップしてプロバイダーやコンテキストを提供
2Parameters の役割Storybook UI やアドオンの動作を制御するメタデータ
3適用レベルグローバル、コンポーネント、ストーリーの 3 段階
4使い分けの基準DOM 構造に影響する場合は Decorators、UI 制御は Parameters
5実践的な活用複数プロバイダーの組み合わせで実務に即した環境を構築

Decorators と Parameters を適切に活用することで、以下のメリットが得られます。

得られる効果:

  • コードの重複を大幅に削減できる
  • 保守性が向上し、設定変更が一箇所で完結する
  • ストーリーファイルの可読性が向上する
  • チーム全体で一貫した開発環境を共有できる

最初はシンプルな背景色やテーマの設定から始めて、プロジェクトの成長に合わせて徐々に Decorator を追加していくアプローチをおすすめします。一度設定すれば、新しいコンポーネントを追加する際にも同じ環境がすぐに利用できるようになり、開発効率が大きく向上するでしょう。

Storybook の Decorators と Parameters を使いこなして、より快適なコンポーネント開発を実現してください。

関連リンク