レビュー済み·難易度: 初級·更新: 2026-04-18

CSS Modules でスコープ付きスタイルのコンポーネントを作る

CSS Modules を使い、クラス名の衝突を避けたスコープ付きスタイルでコンポーネントを実装する例。Tailwind との使い分けと composes による再利用パターンも示す。

nextjsstylingui-component

対応バージョン

nextjs 15react 19

前提環境

CSS の基本とコンポーネントの書き方を理解していること

概要

CSS Modules(.module.css)を使い、クラス名の衝突を避けたスコープ付きスタイルで Button コンポーネントを実装する。composes による基底スタイルの再利用と、バリアントの出し分けパターンを示す。

インストール

# 追加インストールは不要

実装

ベースとなる共通スタイル

/* styles/base.module.css */
.button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0.5rem 1rem;
  border-radius: 0.375rem;
  font-size: 0.875rem;
  font-weight: 500;
  border: none;
  cursor: pointer;
  transition: opacity 0.15s;
}

.button:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}

Button コンポーネントのスタイル

/* components/Button.module.css */
.base {
  composes: button from "../styles/base.module.css";
}

.primary {
  composes: base;
  background-color: #2563eb;
  color: #ffffff;
}

.primary:hover:not(:disabled) {
  background-color: #1d4ed8;
}

.secondary {
  composes: base;
  background-color: #f3f4f6;
  color: #1f2937;
  border: 1px solid #d1d5db;
}

.secondary:hover:not(:disabled) {
  background-color: #e5e7eb;
}

.danger {
  composes: base;
  background-color: #dc2626;
  color: #ffffff;
}

.danger:hover:not(:disabled) {
  background-color: #b91c1c;
}

.sm {
  padding: 0.25rem 0.75rem;
  font-size: 0.75rem;
}

.lg {
  padding: 0.75rem 1.5rem;
  font-size: 1rem;
}

Button コンポーネント

// components/Button.tsx
import styles from "./Button.module.css";

type Variant = "primary" | "secondary" | "danger";
type Size = "sm" | "md" | "lg";

type Props = {
  variant?: Variant;
  size?: Size;
  disabled?: boolean;
  children: React.ReactNode;
  onClick?: () => void;
};

export function Button({
  variant = "primary",
  size = "md",
  disabled,
  children,
  onClick,
}: Props) {
  const classes = [
    styles[variant],
    size !== "md" ? styles[size] : "",
  ]
    .filter(Boolean)
    .join(" ");

  return (
    <button className={classes} disabled={disabled} onClick={onClick}>
      {children}
    </button>
  );
}

Card コンポーネントの例

/* components/Card.module.css */
.card {
  border: 1px solid #e5e7eb;
  border-radius: 0.5rem;
  padding: 1.25rem;
  background-color: #ffffff;
  box-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1);
}

.title {
  font-size: 1rem;
  font-weight: 600;
  color: #111827;
  margin-bottom: 0.5rem;
}

.body {
  font-size: 0.875rem;
  color: #6b7280;
  line-height: 1.6;
}
// components/Card.tsx
import styles from "./Card.module.css";

type Props = {
  title: string;
  children: React.ReactNode;
};

export function Card({ title, children }: Props) {
  return (
    <div className={styles.card}>
      <h3 className={styles.title}>{title}</h3>
      <div className={styles.body}>{children}</div>
    </div>
  );
}

使用例

// app/page.tsx
import { Button } from "@/components/Button";
import { Card } from "@/components/Card";

export default function Page() {
  return (
    <main style={{ padding: "2rem", maxWidth: "600px", margin: "0 auto" }}>
      <Card title="ボタンサンプル">
        <div style={{ display: "flex", gap: "0.75rem", flexWrap: "wrap" }}>
          <Button variant="primary">送信する</Button>
          <Button variant="secondary">キャンセル</Button>
          <Button variant="danger" size="sm">削除</Button>
          <Button variant="primary" disabled>無効</Button>
        </div>
      </Card>
    </main>
  );
}

ポイント

  • .module.css ファイルに書いたクラス名は、ビルド時に Button_primary__xxxx のようなユニークなクラス名に変換される。クラス名の衝突が起きない
  • composes: base; で別クラスのスタイルを継承できる。from "..." を使うと別ファイルのクラスも参照できる
  • Next.js は .module.css を標準サポートしており、追加の設定やインストールは不要
  • Tailwind との使い分け: Tailwind はユーティリティ CSS(クラスを並べる)、CSS Modules はセマンティックなクラス名(コンポーネントの意味を表す)。既存のデザインシステムや CSS を活かしたい場合に CSS Modules が有効
  • TypeScript から import styles from "./Button.module.css" でインポートすると、クラス名の補完が効く(styles.primary 等)

注意点

Next.js は CSS Modules をデフォルトでサポートしており、追加設定不要。Tailwind のようなユーティリティ CSS と対比して理解するとよい。

関連サンプル