JavaScript PR

Tailwind CSSを活用した効率的なクラス管理: clsxとtailwind-mergeの使い方

記事内に商品プロモーションを含む場合があります

Tailwind CSSを使ったプロジェクトでは、クラス名の管理が複雑になりがちです。特に、条件付きクラスやカスタムクラスを多用すると、コードの可読性と保守性が損なわれることがあります。この記事では、これらの問題を解決するための2つのツール clsxtailwind-merge、さらにそれらを組み合わせた cn() 関数の使い方について解説します。


この記事を読んで理解できること

  • clsx の概要とメリット
  • tailwind-merge を使ったTailwind CSSクラスの競合解決
  • cn() 関数を使った効率的なクラス管理
  • 実際の実装例

何のために用いるか

  • clsx: 条件付きでクラス名を設定しやすくするためのライブラリ。
  • tailwind-merge: Tailwind CSSのクラス競合を解決し、意図通りのスタイルを適用するためのツール。
  • cn() 関数: clsxとtailwind-mergeを組み合わせ、シンプルで直感的なクラス管理を実現するための関数。

clsxとは

clsx は、条件付きでクラス名を構築するための軽量ライブラリです。以下のように、複雑な条件でクラス名を動的に切り替えたい場合に役立ちます。

インストール

npm install clsx

基本的な使い方

import clsx from 'clsx';

const isActive = true;
const className = clsx('btn', { 'btn-active': isActive, 'btn-disabled': !isActive });
// className: "btn btn-active"

tailwind-mergeとは

tailwind-merge は、Tailwind CSSのクラス競合を解消するためのツールです。同じプロパティを設定する複数のクラスがある場合、優先順位に従って適切なクラスを選択します。

インストール

npm install tailwind-merge

基本的な使い方

import { twMerge } from 'tailwind-merge';

const className = twMerge('text-red-500 text-blue-500');
// className: "text-blue-500"


cn() 関数の作成

cn() 関数は、clsxとtailwind-mergeを組み合わせた関数で、効率的なクラス名管理を実現します。

実装

import clsx from 'clsx';
import { twMerge } from 'tailwind-merge';

export function cn(...inputs) {
  return twMerge(clsx(inputs));
}

コードの説明

  1. clsx
    • clsx は、条件に基づいて CSS クラス名を動的に生成するライブラリです。
    • 配列やオブジェクト形式でクラス名を渡し、有効なクラスだけを返します。
    • 例えば:
      clsx('btn', { 'btn-primary': isPrimary, 'btn-disabled': isDisabled })
      // isPrimary = true, isDisabled = false の場合
      // => "btn btn-primary"
  2. twMerge
    • tailwind-merge は、Tailwind CSS のクラス名をマージし、競合を解決するライブラリです。
    • Tailwind CSS ではクラス名が競合する場合(例: p-2p-4)があります。このような場合、twMerge は後勝ちで重複を解決します。
    • 例えば:
      twMerge('p-2 p-4')
      // => "p-4"
  3. cn 関数
    • 引数に受け取ったクラス名(配列やオブジェクト形式を含む)を、まず clsx で組み合わせ、次に twMerge で競合を解消して返します。
    • 実際のコードの動作:
      cn('text-lg', { 'text-red-500': isError, 'text-green-500': isSuccess })
      // isError = true の場合
      // => "text-lg text-red-500"

      cn('p-2', 'p-4', { 'bg-blue-500': true })
      // => "p-4 bg-blue-500"

使用例

import { cn } from './utils/cn';

const isActive = true;
const className = cn('btn', { 'btn-active': isActive, 'btn-disabled': !isActive }, 'text-blue-500 text-red-500');
// className: "btn btn-active text-red-500"

cn()関数を使わない場合の課題

条件付きクラス名や競合の管理を手作業で行うと、以下の問題が発生します。

  • クラス名が長くなり、可読性が低下。
  • Tailwind CSSの競合解決が手間。
  • メンテナンスが困難。

cn()関数を使った場合の改善点

効率的なクラス管理

複数の条件や競合を簡潔に記述可能。

const isError = true;
const className = cn('form-input', { 'border-red-500': isError }, 'text-lg font-bold');
// className: "form-input border-red-500 text-lg font-bold"

保守性の向上

シンプルな記述で、チーム全体の理解が容易になります。

実装例

Buttonコンポーネントの実装

import React from 'react';
import { cn } from './utils/cn';

export function Button({ isActive, children }) {
  return (
    <button
      className={cn(
        'px-4 py-2 rounded',
        { 'bg-blue-500 text-white': isActive, 'bg-gray-300 text-black': !isActive }
      )}
    >
      {children}
    </button>
  );
}

Buttonコンポーネントの呼び出し

import React from 'react';
import { Button } from './Button';

export default function App() {
  return (
    <div className="p-4">
      <Button isActive={true}>アクティブ</Button>
      <Button isActive={false}>非アクティブ</Button>
    </div>
  );
}

発展

clsxtailwind-merge の組み合わせ例

clsxtailwind-merge を組み合わせると、条件付きクラスや競合解決を同時に管理できます。

import clsx from 'clsx';
import { twMerge } from 'tailwind-merge';

function cn(...classes: (string | undefined | false | null)[]) {
  return twMerge(clsx(...classes));
}

type Props = {
  isPrimary?: boolean;
  isDisabled?: boolean;
  children: React.ReactNode;
};

export function Button({ isPrimary, isDisabled, children }: Props) {
  return (
    <button
      className={cn(
        'px-4 py-2 rounded',
        isPrimary && 'bg-blue-500 text-white',
        isDisabled && 'opacity-50 cursor-not-allowed'
      )}
      disabled={isDisabled}
    >
      {children}
    </button>
  );
}

Tailwind Config を考慮した利用例

カスタマイズされた Tailwind Config を利用して、特定のテーマカラーを適用する場合の例を追加します。

// tailwind.config.js
module.exports = {
  theme: {
    extend: {
      colors: {
        primary: '#1D4ED8',
        secondary: '#9333EA',
      },
    },
  },
};

カスタムカラーを活用した Button コンポーネント:

export function Button({ isPrimary, children }: Props) {
  return (
    <button
      className={cn(
        'px-4 py-2 rounded',
        isPrimary ? 'bg-primary text-white' : 'bg-secondary text-black'
      )}
    >
      {children}
    </button>
  );
}

Tailwind Variants との比較

Tailwind Variants は、複数の状態やバリアントを管理するのに便利です。

例: Tailwind Variants を使った Button

import { cva } from 'class-variance-authority';

const button = cva('px-4 py-2 rounded', {
  variants: {
    variant: {
      primary: 'bg-primary text-white',
      secondary: 'bg-secondary text-black',
    },
    disabled: {
      true: 'opacity-50 cursor-not-allowed',
      false: '',
    },
  },
  defaultVariants: {
    variant: 'primary',
    disabled: false,
  },
});

type Props = {
  variant?: 'primary' | 'secondary';
  disabled?: boolean;
  children: React.ReactNode;
};

export function Button({ variant, disabled, children }: Props) {
  return <button className={button({ variant, disabled })}>{children}</button>;
}

clsxtailwind-merge を組み合わせた場合と比べて、構造化された管理が可能になります。

まとめ

  • 条件付きクラス名の管理が簡単になる。
  • 可読性が向上し、コードが整理される。

tailwind-mergeを使う利点

  • クラス競合を自動で解消。
  • Tailwind CSSを効率的に活用可能。

定義したcn() 関数の使い方

  • clsxとtailwind-mergeを組み合わせることで、シンプルかつ強力なクラス管理が実現。

clsxtailwind-merge を使えば、Tailwind CSSでのクラス管理が格段に効率化されます。また、cn() 関数を定義すれば、さらに保守性の高いコードを書くことが可能です。この記事を参考に、ぜひプロジェクトで活用してください。

参考資料