© TOOLS BOX — Next.js / React / TypeScript コードサンプル集

サンプルガイド
←サンプル一覧
nextjstesting

Storybook でスケルトンカードコンポーネントのストーリーを書く

スケルトンカード UI(単体・グリッド・loading.tsx 相当)の各状態を Storybook CSF 形式でストーリー化し、animate-pulse の見た目を確認できるカタログを作る例。

難易度: 初級·更新: 2026-04-18·
storybook

対応バージョン

nextjs 15react 19storybook 8

前提環境

Storybook の基本的なストーリー記述(CSF 形式)と tailwind-skeleton-card の実装を理解していること

概要

SampleCardSkeleton(単体)と SampleCardSkeletonGrid(グリッド)の各状態を Storybook CSF 3.0 形式でストーリー化する。count props の違い・グリッド幅・loading.tsx 相当の全画面版など複数バリエーションをカタログにして、animate-pulse の見た目をデザインレビューで確認できるようにする。

インストール

npx storybook@latest init

実装

対象コンポーネント(再掲)

// components/SampleCardSkeleton.tsx
export function SampleCardSkeleton() {
  return (
    <div className="animate-pulse rounded-lg border border-gray-100 p-4 space-y-3">
      <div className="h-5 w-3/4 rounded bg-gray-200" />
      <div className="h-4 w-full rounded bg-gray-200" />
      <div className="h-4 w-5/6 rounded bg-gray-200" />
      <div className="flex gap-2 pt-1">
        <div className="h-5 w-16 rounded-full bg-gray-200" />
        <div className="h-5 w-12 rounded-full bg-gray-200" />
      </div>
    </div>
  );
}

type GridProps = { count?: number };

export function SampleCardSkeletonGrid({ count = 6 }: GridProps) {
  return (
    <div className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3">
      {Array.from({ length: count }).map((_, i) => (
        <SampleCardSkeleton key={i} />
      ))}
    </div>
  );
}

ストーリーファイル

// components/SampleCardSkeleton.stories.tsx
import type { Meta, StoryObj } from "@storybook/react";
import { SampleCardSkeleton, SampleCardSkeletonGrid } from "./SampleCardSkeleton";

const meta = {
  title: "Sample/SampleCardSkeleton",
  component: SampleCardSkeleton,
  tags: ["autodocs"],
  parameters: {
    layout: "padded",
  },
} satisfies Meta<typeof SampleCardSkeleton>;

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

export const Default: Story = {};

export const InCard: Story = {
  decorators: [
    (Story) => (
      <div className="max-w-sm">
        <Story />
      </div>
    ),
  ],
};

グリッドのストーリー

// components/SampleCardSkeletonGrid.stories.tsx
import type { Meta, StoryObj } from "@storybook/react";
import { SampleCardSkeletonGrid } from "./SampleCardSkeleton";

const meta = {
  title: "Sample/SampleCardSkeletonGrid",
  component: SampleCardSkeletonGrid,
  tags: ["autodocs"],
  parameters: {
    layout: "fullscreen",
  },
  argTypes: {
    count: { control: { type: "number", min: 1, max: 12 } },
  },
} satisfies Meta<typeof SampleCardSkeletonGrid>;

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

export const Default: Story = {
  args: { count: 6 },
  decorators: [
    (Story) => (
      <div className="p-6">
        <Story />
      </div>
    ),
  ],
};

export const ThreeItems: Story = {
  args: { count: 3 },
  decorators: [
    (Story) => (
      <div className="p-6">
        <Story />
      </div>
    ),
  ],
};

export const SingleItem: Story = {
  args: { count: 1 },
  decorators: [
    (Story) => (
      <div className="p-6 max-w-sm">
        <Story />
      </div>
    ),
  ],
};

loading.tsx 相当のページレベルストーリー

// app/samples/Loading.stories.tsx
import type { Meta, StoryObj } from "@storybook/react";
import { SampleCardSkeletonGrid } from "@/components/SampleCardSkeleton";

const meta = {
  title: "Pages/SamplesLoading",
  component: SampleCardSkeletonGrid,
  parameters: {
    layout: "fullscreen",
  },
} satisfies Meta<typeof SampleCardSkeletonGrid>;

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

export const LoadingPage: Story = {
  args: { count: 9 },
  decorators: [
    (Story) => (
      <main className="mx-auto max-w-5xl px-4 py-8">
        <div className="mb-6 h-8 w-48 animate-pulse rounded bg-gray-200" />
        <Story />
      </main>
    ),
  ],
};

ポイント

  • satisfies Meta<typeof Component> で props 型を推論させる。as Meta より型安全で、args のオートコンプリートが利く
  • tags: ["autodocs"] を付けると Storybook が args と argTypes から自動でドキュメントページを生成する。count props の説明を補足するには argTypes の description フィールドを追加する
  • decorators でラッパーを加えることでコンポーネント自体は変えずに、表示幅や padding などのコンテキストを各ストーリーで調整できる
  • argTypes: { count: { control: { type: "number" } } } で Controls パネルからインタラクティブに件数を変更できる。デザイナーが Storybook 上で件数バリエーションを確認しやすくなる
  • animate-pulse は Tailwind の CSS アニメーションのためストーリー上でもアニメーションが動く。静止画の確認ではなく実際の動きを見てレビューできる
  • ページレベルのストーリー(LoadingPage)を作ることで、loading.tsx が実際に表示される状態をデザインレビューで再現できる。Suspense 境界や loading.tsx 単体のレイアウト確認に使う

注意点

storybook-empty-state-story は EmptyState の story 整理。storybook-filter-chip-story はフィルタチップの story 整理。これは skeleton card UI の各表示状態(単体・グリッド・数量違い)をカタログ化するストーリーに特化。

関連サンプル

同じテーマや技術スタックを使った実装例

  • Storybook でフィルタチップコンポーネントのストーリーを書く

    フィルタチップ(アクティブ状態・削除ボタン付き・複数選択)の各状態を Storybook CSF 形式でストーリー化し、args と play 関数でインタラクションを検証する例。

  • Storybook で EmptyState コンポーネントの story とバリエーションを管理する

    EmptyState コンポーネントの検索ゼロ・未登録・エラーの 3 バリアントを Storybook の story として定義し、args / argTypes でプロパティを切り替えながら確認できる例。

  • Storybook でコンポーネントの Story を書く

    Storybook 8 の CSF3 形式で UI コンポーネントの Story を作成する例。args / argTypes / play 関数を使ったインタラクティブテストのパターンを示す。

  • Tailwind CSS でローディング中のスケルトンカード UI を実装する

    コンテンツ読み込み中に表示するスケルトン(骨格)カード UI を Tailwind CSS の animate-pulse で実装し、一覧グリッドに並べるパターン。

  • Storybook の play 関数でインタラクションテストを書く

    @storybook/test の userEvent と expect を使い、play 関数でユーザー操作をシミュレートしてコンポーネントの動作を Story 内で自動検証する例。

関連仕様

このサンプルを理解するのに役立つ仕様や概念

  • FrameworkNext.jsReact ベースのフルスタックフレームワーク。SSR・SSG・App Router・API Routes を提供する。
←サンプル一覧に戻る