💭

Tailwind CSS × React に cn が必要な理由

に公開

はじめに

Tailwind CSSReact を組み合わせて開発していると、次のようなコードを頻繁に目にします。

<div className="px-4 py-2 text-sm font-medium text-white bg-blue-500 hover:bg-blue-600 rounded">

最初は問題ありませんが、条件分岐や状態ごとのスタイルが増えてくると、className はすぐに破綻します。

本記事では、Tailwind CSS × React の現場でよく使われる cn というユーティリティが
なぜ事実上必須になるのかを、具体例とともに解説します。

cn とは

cnclassName を安全かつ読みやすく組み立てるためのユーティリティ関数です。
React + Tailwind 界隈では、以下のような役割を担います。

  • 条件付きクラスの整理
  • className の可読性向上
  • Tailwind クラスの競合解決(tailwind-merge と併用)

cn はライブラリ名ではない

重要なポイントとして、cn自体は公式ライブラリではありません。
多くの場合、以下のようなライブラリをラップした自作関数の名前として使われています。

  • clsx または classnames
  • tailwind-merge
import { clsx } from "clsx"
import { twMerge } from "tailwind-merge"

export function cn(...inputs: ClassValue[]) {
  return twMerge(clsx(inputs))
}

つまり、
「cn = className を扱うための慣習的な関数名」
という理解が正確です。

Tailwind CSS で起きがちな className の問題

className が長く、読めなくなる

<button className="px-4 py-2 text-sm font-medium text-white bg-blue-500 rounded hover:bg-blue-600 disabled:bg-gray-300 disabled:cursor-not-allowed">
  • 見た瞬間に意図が分からない
  • 状態が増えるとさらに肥大化

条件分岐が入ると破綻する

<button
  className={
    isActive
      ? "px-4 py-2 bg-blue-500 text-white"
      : "px-4 py-2 bg-gray-200 text-black"
  }
>
  • 共通クラスの重複
  • 差分が分かりづらい
  • 条件追加が怖い

条件分岐・バリアント管理の難しさ

状態が増えるほど、className はロジックとして破綻します。

className={
  isActive
    ? isDisabled
      ? "bg-gray-300 text-gray-500"
      : "bg-blue-500 text-white"
    : "bg-white text-black"
}

この時点で、保守性は著しく低下します。

cn を使った書き方(Before / After)

Before:テンプレートリテラル地獄

<button
  className={`px-4 py-2 rounded ${
    isActive ? "bg-blue-500 text-white" : "bg-gray-200 text-black"
  } ${isDisabled ? "opacity-50 cursor-not-allowed" : ""}`}
>

After:cn を使う

<button
  className={cn(
    "px-4 py-2 rounded",
    isActive && "bg-blue-500 text-white",
    !isActive && "bg-gray-200 text-black",
    isDisabled && "opacity-50 cursor-not-allowed"
  )}
>

メリット

  • 共通部分と条件部分が分離される
  • JSX が読みやすい
  • 条件追加が怖くない

tailwind-merge との相性が抜群な理由

Tailwind 特有の「クラス競合」

cn(
  "bg-blue-500",
  isDanger && "bg-red-500"
)

このままでは、両方のクラスが残ります。

tailwind-merge がやってくれること

cn("bg-blue-500", "bg-red-500")
// => "bg-red-500"
  • Tailwind の仕様を理解した上で
  • 後勝ちで不要なクラスを削除
    => 条件付きスタイルを安心して書けるようになります。

大規模開発で cn が必須になる理由

可読性の維持

  • JSX がスタイルロジックとして読める
  • className が「意味のある構造」になる

変更耐性の向上

  • デザイン変更時に影響範囲を追いやすい
  • 不要なクラスが残らない

チーム開発との相性

  • 書き方が統一される
  • レビューしやすい
  • 属人化しにくい

まとめ

  • cn は className を組み立てるための慣習的ユーティリティ
  • Tailwind CSS × React では className が複雑化しやすい
  • cn + tailwind-merge によって安全性と可読性が向上する
  • 小さな関数だが、開発体験への影響は非常に大きい
    Tailwind を本格的に使うなら、cn は「最初に入れるべきツール」です。

Discussion