Tailwind CSSを使ったプロジェクトでは、クラス名の管理が複雑になりがちです。特に、条件付きクラスやカスタムクラスを多用すると、コードの可読性と保守性が損なわれることがあります。この記事では、これらの問題を解決するための2つのツール clsx と tailwind-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));
}
コードの説明
clsx
clsx
は、条件に基づいて CSS クラス名を動的に生成するライブラリです。- 配列やオブジェクト形式でクラス名を渡し、有効なクラスだけを返します。
- 例えば:
clsx('btn', { 'btn-primary': isPrimary, 'btn-disabled': isDisabled })
// isPrimary = true, isDisabled = false の場合
// => "btn btn-primary"
twMerge
tailwind-merge
は、Tailwind CSS のクラス名をマージし、競合を解決するライブラリです。- Tailwind CSS ではクラス名が競合する場合(例:
p-2
とp-4
)があります。このような場合、twMerge
は後勝ちで重複を解決します。 - 例えば:
twMerge('p-2 p-4')
// => "p-4"
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>
);
}
発展
clsx
と tailwind-merge
の組み合わせ例
clsx
と tailwind-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>;
}
clsx
と tailwind-merge
を組み合わせた場合と比べて、構造化された管理が可能になります。
まとめ
- 条件付きクラス名の管理が簡単になる。
- 可読性が向上し、コードが整理される。
tailwind-mergeを使う利点
- クラス競合を自動で解消。
- Tailwind CSSを効率的に活用可能。
定義したcn()
関数の使い方
- clsxとtailwind-mergeを組み合わせることで、シンプルかつ強力なクラス管理が実現。
clsx
と tailwind-merge
を使えば、Tailwind CSSでのクラス管理が格段に効率化されます。また、cn()
関数を定義すれば、さらに保守性の高いコードを書くことが可能です。この記事を参考に、ぜひプロジェクトで活用してください。