【ダークモード対応も簡単！】Tailwindで繰り返し作業を減らすカスタムクラス作成術（コード例付き）

# Tailwindでカスタムクラスを作成する方法とユーティリティレイヤーの活用
私は`Next.js`と`tailwindcss`を使用し、フロントエンド開発を行っている初心者です。
Tailwindは便利なユーティリティクラスを提供していますが、同じスタイルの組み合わせを何度も書く必要があり、コードが冗長になってしまう課題がありました。例えば、ホバー時のスタイルやダークモード対応など、繰り返し使用するスタイルをより効率的に管理したいと考えていました。

今回は、その解決策として、Tailwindのカスタムクラス作成方法とユーティリティレイヤーの活用について調べたことをまとめます。

## 目次

1. [はじめに](#はじめに)

2. [カスタムクラスの基本的な使用例](#カスタムクラスの基本的な使用例)

3. [`apply`とは](#applyとは)
  
4. [ユーティリティレイヤーの詳細説明](#ユーティリティレイヤーの詳細説明)
  
5. [トラブルシューティング](#トラブルシューティング)

6. [発展的な使用方法](#発展的な使用方法)

7. [まとめ](#まとめ)
 
8. [参考文献](#参考文献)

## はじめに
TailwindCSSは、HTMLに直接スタイルクラスを書けるCSSフレームワークです。
普段のTailwindCSS開発では、以下のような方法でスタイリングを行います。

- コンポーネントごとにクラス名を書く
- ホバーやダークモード用のクラスも追加する
- 同じデザインを使いまわす時は、クラスをコピー&ペーストする

しかし、この方法だと以下のような問題が出てきます。

- 同じクラスの組み合わせを何度も書かないといけない-
- ホバーやダークモードの設定が増えると、クラス名が長くなりすぎる
- サイト全体でデザインを統一するのが難しい

この記事では、これらの問題を解決するためのカスタムクラスの作り方をまとめます。

## カスタムクラスの基本的な使用例

### 最もシンプルな実装例

`globals.css`にカスタムクラスを追加します。

#### globals.css
```css
@tailwind base;
@tailwind components;
@tailwind utilities;

@layer utilities {
  .hover-effect {
   /* マウスを合わせると青色のテキストと薄い青の背景にスムーズに変化 */
    @apply hover:text-blue-500 hover:bg-blue-50 transition-colors duration-200;
  }
}
```

使用したい箇所に`globals.css`で定義した`hover-effect`を記載します。
#### 使用例
```jsx
function NavButton() {
  return (
    <button className="hover-effect px-4 py-2 rounded-md">
      ボタン
    </button>
  );
}
```

## applyとは
TailwindCSSがユーザーに提供する特別な指示文（ディレクティブ）です。この指示文を使うことで、Tailwindが用意している便利なクラスをカスタムクラスの中で使えるようになります。
つまり、TailwindCSSのユーティリティクラスを適用（apply）させるということです。`@apply`と記述がない限り、ユーティリティクラスとして認識されません。

```css
/* @applyを使わない場合（認識されない） */
.my-button {
  bg-white p-4;  // これは動作しない
}

/* @applyを使う場合（正しく認識される） */
.my-button {
  @apply bg-white p-4;  // これは動作する
}
```

## ユーティリティレイヤーの詳細説明

### レイヤーの種類と役割

`@layer`を使用すると、Tailwind CSS 内で定義するスタイルの優先順位を明確に管理することができます。レイヤーには base, components, utilities という3つの層があり、それぞれ以下の優先順位で適用されます。

1. **baseレイヤー** (`@layer base`)
   - 優先順位：低
   - 使用タイミング:サイト全体の基本スタイルを設定する時
   - 例：サイト全体のフォント設定、見出し、リンク
   - ダークモードの基本設定を行う時なども便利

2. **componentsレイヤー** (`@layer components`)
   - 優先順位：中
   - 使用タイミング:特定のパターンを持つUI要素をまとめる時
   - 例：ボタン、カード、ナビゲーション
   - 繰り返し使用される複合的なスタイルをコンポーネント化する際に最適

3. **utilitiesレイヤー** (`@layer utilities`)
   - 優先順位：高
   - 使用タイミング：頻繁に再利用する独自のユーティリティクラスを作成する時
   - 例：カスタムのホバーエフェクト、アニメーション、レスポンシブパターン
   - 既存のユーティリティクラスを上書きしたい時にも最適（最も高い優先順位を持つため）

#### 使用例
```css
@layer base {
  /* サイト全体の見出しスタイルを統一的に設定し、ダークモードにも対応 */
  h1 {
    @apply text-2xl font-bold mb-4 text-gray-900 dark:text-gray-100;
  }
}

@layer components {
  /* プロジェクト全体で使用する基本的なボタンスタイルを定義 */
  .btn-primary {
    @apply px-4 py-2 bg-blue-500 text-white rounded-md hover:bg-blue-600;
  }
}

@layer utilities {
  /* テキストにグラデーションエフェクトを適用する汎用的なユーティリティクラス */
  .text-gradient {
    @apply bg-clip-text text-transparent bg-gradient-to-r from-blue-500 to-purple-500;
  }
}
```
#### 優先順位の違い
下記の様に同じ`.button`クラスに対して`components`レイヤーと`utilities`レイヤーで異なる背景色を設定すると、優先順位のルールに従い、utilities レイヤー（優先順位：高）が components レイヤー（優先順位：中）より優先されるため、最終的にボタンの背景色は赤色になります。

##### globals.css
```css
@layer components {
  /* 背景：青 */
  .button {
    background-color: blue;
  }
}

@layer utilities {
     /* 背景：赤 */
  .button {
    background-color: red;
  }
}
```

使用したい箇所にglobals.cssで定義した`button`を記載します。

##### 使用例
```jsx
function NavButton() {
  return (
    // 背景は赤色になる（utilitiesが適用される）
    <button className='button px-4 py-2 rounded-md'>ボタン</button>
  );
}

```

このように、`@layer`を使用することで、同じセレクタに対して異なるレイヤーで定義されたスタイルがある場合、明確な優先順位に基づいて適用されるスタイルが決定されます。これにより、CSSの詳細度の問題を回避しつつ、より予測可能で管理しやすいスタイリングが可能になります。

### 実践的な使用例

#### ダークモード対応のカスタムクラス
```css
@layer utilities {
　 /* カードのデザイン：白背景・角丸、ホバー、ダークモードにも自動対応 */
  .custom-card {
    @apply 
      /* 基本スタイル */
      bg-white p-4 rounded-lg
      /* ライトモードのホバー */
      hover:bg-gray-50 hover:text-blue-500
      /* ダークモード */
      dark:bg-gray-800 dark:text-gray-200
      /* ダークモードのホバー */
      dark:hover:bg-gray-700 dark:hover:text-blue-400
      /* アニメーション */
      transition-all duration-200;
  }
}
```

## トラブルシューティング

### よくある問題と解決方法

#### カスタムクラスが適用されない
   - 原因：PurgeCSSの設定不備
   - 解決：`tailwind.config.js`のcontentの設定を確認

```javascript
// tailwind.config.js
module.exports = {
  content: [
    "./src/**/*.{js,ts,jsx,tsx}",
  ],
  // ...
}
```

#### 優先順位の問題
   - 原因：レイヤーの使用誤り
   - 解決：適切なレイヤーで定義する

### 開発環境での注意点
- 変更された部分だけを自動的に更新する機能(Hot Reloading)でスタイルが反映されない場合は、開発サーバーの再起動が必要
- VSCodeで拡張機能の使用 (Tailwind CSS IntelliSense)

## 発展的な使用方法

### レスポンシブ対応
```css
@layer utilities {
　/* 画面サイズに応じてパディングを自動調整(スマホ:小/PC:大) */
  .responsive-padding {
    @apply p-2 md:p-4 lg:p-6;
  }
```

### 操作に応じて見た目が変わる（インタラクティブ）な状態管理

#### globals.css
```css
  /* ボタンの状態(有効/無効)に応じて見た目を自動変更(無効時：マウスカーソルが禁止マーク) */
  .state-variants {
    @apply 
      enabled:hover:bg-blue-500 
      disabled:bg-gray-300 
      disabled:cursor-not-allowed;
  }
}
```

使用したい箇所にglobals.cssで定義した`state-variants`を記載します。

#### 使用例
```jsx
// 送信ボタン：ボタンの状態（読み込み中かどうか）を受け取るコンポーネント
function SubmitButton({ isLoading }) {
  return (
    <button // disabled属性でボタンの有効/無効を切り替え
      disabled={isLoading}
      // state-variantsクラスで状態に応じたスタイルを適用
      className='state-variants p-2 rounded-md'
    >
      {/* 読み込み中は「送信中...」、それ以外は「送信する」と表示（三項演算子） */}
      {isLoading ? '送信中...' : '送信する'}
    </button>
  );
}

// フォームの実装例
function ContactForm() {
  // useStateで読み込み状態を管理（初期値はfalse）
  const [isLoading, setIsLoading] = useState(false); // フォーム送信時の処理

  const handleSubmit = async (e) => {
    // フォームのデフォルトの送信動作を防止
    e.preventDefault();

    // 送信開始時に読み込み状態をtrueに
    setIsLoading(true);

    // フォームのデータを送信（実際の送信処理はここに記述）
    await submitForm();

    // 送信完了後に読み込み状態をfalseに
    setIsLoading(false);
  };

  return (
    // onSubmitでフォーム送信時の処理を指定
    <form onSubmit={handleSubmit}>
      {/* フォームの入力項目などがここに入ります */}
      {/* 読み込み状態をSubmitButtonに渡す */}
      <SubmitButton isLoading={isLoading} />
    </form>
  );
}
```

ボタンの状態に応じて自動的にスタイルが切り替わり、ユーザーに対して視覚的なフィードバックを提供できます。

### カスタムプロパティとの連携
```css
@layer utilities {
　 /* アニメーションの動きをカスタム変数で柔軟に制御可能 */
  .custom-animation {
    @apply transition-all duration-[var(--duration)] ease-[var(--ease)];
  }
}
```

## まとめ

カスタムクラスとユーティリティレイヤーの活用により
- コードの重複を削減
- スタイルの一貫性を維持
- メンテナンス性の向上
- 開発効率の改善

が実現できます。

特に、ダークモード対応やレスポンシブデザインの実装において、その効果を発揮します。

もし記事の内容に間違いがあれば、コメントでご指摘いただけますと幸いです。また、より良い方法や代替手段をご存知の方がいらっしゃいましたら、ぜひ共有していただければと存じます。
例えば、以下のような点について、皆様の知見やベストプラクティスをお聞かせいただければ幸いです。

- カスタムクラスの命名規則
- プロジェクトでの具体的な活用事例
- パフォーマンス最適化のテクニック
- その他のTailwind CSSの活用方法

## 参考文献
- [Tailwind CSS公式ドキュメント](https://tailwindcss.com/docs)