Blog

clsx 다음에 tw-merge가 필요한 이유


6 min read

서론#

clsx를 사용하면
조건부 클래스를 문자열 템플릿보다 훨씬 읽기 쉽게 정리할 수 있다.

className={clsx(
  "p-4 text-sm",
  isLarge && "p-8 text-lg"
)}

코드는 깔끔해졌고,
조건에 따라 클래스가 잘 합쳐지는 것처럼 보였다.

그런데 결과 클래스를 다시 보면
조금 애매한 지점이 남아 있었다.

clsx만 사용할 때 생기는 문제#

위 코드가 만들어내는 최종 클래스 문자열은 다음과 같다.

p-4 text-sm p-8 text-lg

Tailwind에서는
뒤에 선언된 클래스가 우선 적용되기 때문에
결과 화면은 의도대로 동작한다.

하지만 코드 관점에서는 몇 가지 문제가 남는다.

  • 실제로 어떤 클래스가 적용되는지 한눈에 보이지 않음
  • 서로 충돌하는 클래스가 그대로 남아 있음
  • “의도된 최종 상태”가 문자열에 드러나지 않음

이 지점에서
clsx는 조건 처리는 해주지만,
클래스 충돌 자체는 해결하지 않는다는 한계가 드러난다.

Tailwind에서 클래스 충돌이란?#

Tailwind는 유틸리티 클래스 기반이기 때문에
같은 범주의 클래스가 동시에 존재할 수 있다.

p-4  p-8
text-sm  text-lg

이 중 실제로 적용되는 것은 하나뿐이지만,
문자열에는 둘 다 남아 있게 된다.

브라우저 입장에서는 문제가 없지만,
개발자 입장에서는 결과를 추론해야 하는 코드가 된다.

tw-merge는 무엇을 해결하는가?#

tw-merge
Tailwind의 클래스 규칙을 알고 있는 상태에서
의미상 충돌하는 클래스를 정리해주는 도구다.

import { twMerge } from "tailwind-merge";

twMerge("p-4 text-sm", "p-8 text-lg");
// 결과: "p-8 text-lg"

여기서 중요한 점은
단순히 중복 문자열을 제거하는 것이 아니라,
Tailwind 기준에서 충돌하는 클래스를 판단한다는 것이다.

clsx와 tw-merge를 함께 쓰는 이유#

두 도구의 역할은 명확히 다르다.

  • clsx
    • 조건에 따라 클래스를 합친다
  • tw-merge
    • 합쳐진 클래스 중 충돌을 정리한다

그래서 실제로는
두 도구를 함께 쓰는 경우가 많다.

className={twMerge(
  clsx(
    "p-4 text-sm",
    isLarge && "p-8 text-lg"
  )
)}

이 구조에서는

  1. clsx가 조건부 클래스를 안전하게 합치고
  2. tw-merge가 최종 결과를 정리한다

결과적으로
의도가 드러나는 클래스 문자열만 남게 된다.

cn 유틸로 clsx + tw-merge를 묶는 패턴#

매번 위 코드처럼 작성해도 문제는 없다.

다만 컴포넌트가 늘어나면 같은 패턴이 반복되고,
코드베이스 전체에서 클래스 합치는 방식이 제각각이 될 수 있다.

그래서 보통은 clsxtw-merge를 한 번 묶어
cn 같은 유틸 함수로 통일해서 사용한다.

import { twMerge } from "tailwind-merge";
import clsx, { type ClassValue } from "clsx";

export const cn = (...inputs: ClassValue[]) => twMerge(clsx(inputs));

사용할 때는 이렇게 바뀐다.

<div className={cn("p-4 text-sm", isLarge && "p-8 text-lg")} />

이 방식의 장점은 단순하다.

  • className 작성 방식이 프로젝트 전체에서 통일됨
  • 매번 twMerge(clsx())를 쓰지 않아도 되어 가독성이 좋아짐
  • “조건 합치기 + 충돌 정리”를 항상 같은 규칙으로 처리할 수 있음

언제까지 clsx만으로 충분할까?#

모든 경우에 tw-merge가 필요한 것은 아니다.

다음과 같은 상황에서는
clsx만으로도 충분하다.

  • 조건이 거의 없는 단순 컴포넌트
  • 충돌 가능성이 없는 클래스 조합
  • 한 가지 상태만 바뀌는 경우

반대로, 아래와 같은 경우에는
tw-merge를 고려하게 된다.

  • 사이즈, variant처럼 같은 범주의 클래스가 겹칠 때
  • 상태에 따라 기본 스타일을 덮어쓸 때
  • 최종 적용 클래스를 코드에서 명확히 드러내고 싶을 때

기준은 성능이나 규칙이 아니라,
읽는 사람이 결과를 바로 이해할 수 있는지 여부다.

Tailwind 동작 방식과의 연결#

tw-merge가 필요한 이유는
Tailwind의 설계 방식과도 연결된다.

  • Tailwind는 런타임이 아닌 빌드 타임 기준
  • 클래스 우선순위는 문자열 순서에 의존
  • 충돌을 “에러”로 취급하지 않음

이 전제 위에서
tw-merge는 Tailwind를 보완하는 도구라기보다,
Tailwind 사용을 전제로 한 정리 도구에 가깝다.

정리#

  • clsx는 조건부 클래스를 다루기 쉽게 만든다
  • tw-merge는 그 결과를 Tailwind 기준으로 정리한다
  • 둘은 대체 관계가 아니라, 역할이 다른 도구다

Tailwind를 사용하다 보면
“동작은 하지만 찝찝한 클래스 문자열”을 자주 마주치게 된다.

tw-merge는 그 찝찝함을
코드 수준에서 정리해주는 선택지다.

그래서 이 도구는
Tailwind를 깊이 이해할수록
자연스럽게 필요해지는 도구라고 느꼈다.

npm 바로가기#

4