Blog
clsx 다음에 tw-merge가 필요한 이유
서론#
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"
)
)}
이 구조에서는
clsx가 조건부 클래스를 안전하게 합치고tw-merge가 최종 결과를 정리한다
결과적으로
의도가 드러나는 클래스 문자열만 남게 된다.
cn 유틸로 clsx + tw-merge를 묶는 패턴#
매번 위 코드처럼 작성해도 문제는 없다.
다만 컴포넌트가 늘어나면 같은 패턴이 반복되고,
코드베이스 전체에서 클래스 합치는 방식이 제각각이 될 수 있다.
그래서 보통은 clsx와 tw-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개
prettier-plugin-tailwindcss는 무엇을 해결하는가
Tailwind CSS를 사용하며 클래스 순서가 제각각이 되는 문제를 정리합니다. prettier-plugin-tailwindcss가 클래스 정렬을 어떻게 자동화하는지, 왜 사람이 관리하지 않아도 되는 문제인지 학습 관점에서 설명합니다.
clsx / classnames를 언제, 왜 사용하는가
Tailwind CSS를 사용하며 조건부 클래스가 늘어날 때, clsx와 classnames를 언제, 왜 사용하게 되는지 정리합니다. 문자열 기반 클래스 처리의 한계와 정리 기준을 학습 관점에서 설명합니다.
Tailwind 기준에서 본 모바일 퍼스트 vs 데스크탑 퍼스트
모바일 퍼스트와 데스크탑 퍼스트의 차이를 Tailwind CSS의 반응형 설계 방식 기준으로 정리합니다. Tailwind에서 기본 스타일과 반응형 클래스가 어떤 의미를 가지는지 학습 관점에서 설명합니다.
Tailwind CSS란 무엇이고, 왜 많이 사용되는가
Tailwind CSS의 기본 개념과 Utility-First 방식이 무엇인지 정리합니다. 기존 CSS 작성 방식과의 차이, 클래스 조합 방식이 왜 선택되는지 학습 관점에서 설명합니다.