C.W.K.
Stream
Lesson 04 of 07 · published

tailwind-merge + clsx — cn() 헬퍼

~11 min · tailwind-merge, clsx, cn, className

Level 0React 입문자
0 XP0/54 lessons0/12 achievements
0/100 XP to next level100 XP to go0% complete
모든 React 컴포넌트 라이브러리에 작은 cn() 헬퍼 있어. 어디서나 같은 두 줄. 존재 이유 알면 본인 부서진 버전 안 발명하게 돼.

cn() 이 푸는 두 문제

  1. 조건부 클래스 — 가끔 className="px-4 py-2 disabled:opacity-50" 원하고, prop 설정될 때만 추가 클래스. Naive template literal 은 빠르게 추해짐.
  2. 호출자 override — 본인 Button 이 내부에서 className="px-4" 설정, 호출자가 className="px-6" 전달, 둘 다 DOM 에 px-4 px-6 으로 land, Tailwind cascade 가 둘째로 로드된 거 고름. 호출자 의도 조용히 잃음.

두 라이브러리

  • clsx — 조건부 + array/object 클래스 합성 처리. 문자열, 배열, `{ [class]: condition }` 객체의 어떤 조합도 받고 깔끔한 공백 구분 문자열 반환.
  • tailwind-merge — Tailwind 문법 알아. 충돌 유틸리티 둘 (px-4px-6) 주면 마지막 거 유지.

합치면 cn() — 캐논 헬퍼.

cn() 쓰는 자리

src/lib/cn.ts 에 한 번. 모든 컴포넌트 import. cwkPippa 의 tailwind-merge (package.json) 가 정확히 이거 위한 거 — 채팅 UI, sidebar, council, admin 패널 전반에 쓰는 단일 cn().

클래스 문자열을 + 또는 template literal 로 concat 중이면 cn() 을 잘못 재발명 중. 두 함수 작고, 잘 테스트 됨, 서로 의존성 0. 둘 다 설치하고 넘어가.

Code

캐논 cn.ts 파일·ts
// src/lib/cn.ts
import { clsx, type ClassValue } from "clsx";
import { twMerge } from "tailwind-merge";

export function cn(...inputs: ClassValue[]): string {
  return twMerge(clsx(inputs));
}
조건부 + override 가능 클래스에 cn() 쓰는 컴포넌트·tsx
import { cn } from "@/lib/cn";

type ButtonProps = {
  variant?: "primary" | "ghost";
  size?: "sm" | "md" | "lg";
  disabled?: boolean;
  className?: string;
  children: React.ReactNode;
};

export function Button({ variant = "primary", size = "md", disabled, className, children }: ButtonProps) {
  return (
    <button
      disabled={disabled}
      className={cn(
        // Base — 항상 적용
        "inline-flex items-center justify-center rounded-lg font-medium transition-colors",
        // Variant — 하나 선택
        {
          "bg-brand text-bg hover:bg-brand-strong": variant === "primary",
          "bg-transparent text-fg hover:bg-bg-elevated": variant === "ghost",
        },
        // Size — 하나 선택
        {
          "px-3 py-1.5 text-sm": size === "sm",
          "px-4 py-2 text-base": size === "md",
          "px-6 py-3 text-lg": size === "lg",
        },
        // Disabled 상태
        disabled && "opacity-50 cursor-not-allowed",
        // 호출자 override — tailwind-merge 가 충돌 유틸리티 올바르게 해결
        className
      )}
    >
      {children}
    </button>
  );
}

// 호출자가 패딩 override 가능, 안 싸움:
// <Button size="md" className="px-8">Bigger</Button>
// → tailwind-merge 가 px-4 (size=md 의) 떨어뜨리고 px-8 유지.

External links

Exercise

Bootstrap 프로젝트에 clsx + tailwind-merge 설치. src/lib/cn.ts 생성. className prop 받는 기존 컴포넌트를 cn(internalClasses, className) 쓰게 리팩토링. 호출자에서 충돌 Tailwind 유틸 전달해서 호출자 이기는지 테스트.
Hint
테스트 케이스: <Card className="bg-red-500"> 가 Card 가 내부에서 bg-bg-elevated 설정해도 빨간 배경으로 렌더. cn() 없으면 둘 다 element 에 있고 cascade 가 하나 고름. cn() 있으면 tailwind-merge 가 호출자 거 유지.

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고

댓글 0

🔔 답글 알림 (로그인 필요)
로그인댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.