최근 프론트엔드 개발에서는 Next.js와 Tailwind CSS 조합이 채용공고에서 자주 등장할 만큼 인기 있는 기술 스택으로 자리 잡았습니다. 특히 AI 서비스인 바이브코딩과 같은 프로젝트에서도 이 두 기술을 함께 사용하는 사례를 쉽게 찾아볼 수 있습니다. 이런 흐름을 보며 저 역시 Next.js와 Tailwind CSS를 본격적으로 공부하게 되었고, 그 과정에서 Tailwind CSS에서의 조건부 스타일링 방법에 대해 궁금증이 생겼습니다.
기존의 CSS-in-JS 환경에서는 props를 활용해 손쉽게 조건부 스타일을 적용할 수 있었지만, Tailwind CSS는 클래스 기반 스타일링 방식이기 때문에 조건부 스타일을 적용할 때 문자열 안에서 로직을 처리해야 하는 불편함이 있었습니다. 특히 백틱(`)을 활용한 긴 클래스 문자열 안에서 조건 분기를 다루다 보면 가독성이 급격히 떨어지는 문제를 경험했습니다.
이 문제를 해결할 방법을 찾던 중, shadcn/ui에서 사용하는 패턴을 참고하게 되었고, 이를 통해 Tailwind에서 조건부 스타일링을 더 효율적으로 관리하는 방법을 익히게 되었습니다.
조건부 스타일링을 위한 유틸 만들기
Tailwind CSS에서 조건부 클래스 조합과 효율적인 클래스 관리를 위해 보통 유틸리티 함수를 만들어 사용합니다. 그전에, 이 유틸을 만들 때 사용하는 대표적인 라이브러리 두 가지를 먼저 소개합니다.
clsx: 조건부 클래스 조합을 위한 유틸리티
깃허브 바로가기
clsx는 조건에 따라 className 문자열을 동적으로 생성해주는 경량 라이브러리입니다. 배열, 객체, 불리언 등 다양한 형태로 클래스를 조합할 수 있어, 조건부 스타일링이 필요한 컴포넌트에서 매우 유용하게 활용됩니다.
import clsx from 'clsx';
// 단순 문자열
clsx('foo', 'bar'); // → 'foo bar'
// 조건부 객체
clsx({ 'active': true, 'disabled': false }); // → 'active'
// 배열 형태
clsx(['foo', true && 'bar']); // → 'foo bar'
tailwind-merge: Tailwind 클래스 충돌 자동 정리
깃허브 바로가기
tailwind-merge는 Tailwind CSS에서 같은 속성이 여러 번 등장할 때, 중복되거나 충돌하는 클래스를 자동으로 정리해주는 라이브러리입니다. 일반적으로 브라우저는 마지막에 선언된 클래스를 우선 적용하지만, 빌드 도구, 코드 포매터, 조건부 렌더링 등 다양한 상황에서 클래스 순서가 꼬이면 의도와 다르게 스타일이 적용될 수 있습니다.
// tailwind-merge 없이
className='px-2 px-8' // → 실제로는 둘 다 남아 있고, 일반적으로 px-8이 적용됨
// 하지만 순서가 바뀌면 px-2가 적용될 수도 있음
// tailwind-merge 사용
import { twMerge } from 'tailwind-merge';
twMerge('px-2 px-4 bg-red-500 bg-blue-500') // → 'px-4 bg-blue-500'
tailwind-merge는 이런 중복을 제거해, 항상 각 속성별로 마지막 값만 남겨주므로
예상치 못한 스타일 충돌을 방지할 수 있습니다.
왜 clsx와 tailwind-merge를 함께 사용하는 것이 유용할까?
- clsx: 조건부 클래스 조합의 편리함
clsx는 객체, 배열, 불리언 등 다양한 형태로 전달된 값을 조건에 따라 className 문자열로 변환해줍니다.
이를 통해 컴포넌트의 props나 상태에 따라 클래스를 동적으로 조합할 수 있어, 코드의 가독성과 관리가 훨씬 쉬워집니다.
import clsx from 'clsx';
const className = clsx(
'base-class',
{ 'active-class': isActive, 'disabled-class': isDisabled },
'extra-class'
);
// 결과: 'base-class active-class extra-class'
- tailwind-merge: Tailwind 클래스 충돌 자동 해결
tailwind-merge는 Tailwind CSS의 유틸리티 클래스 중복이나 충돌을 자동으로 정리해줍니다.
예를 들어, bg-red-500 bg-blue-500처럼 같은 속성의 클래스가 여러 번 들어가면, Tailwind의 우선순위 규칙에 따라 마지막 클래스만 남기고 나머지는 제거합니다.
import { twMerge } from 'tailwind-merge';
const className = twMerge('bg-red-500 text-white', 'bg-blue-500 px-4');
// 결과: 'bg-blue-500 text-white px-4'
라이브러리를 활용해 유틸 만들기
이 유틸은 shadcn/ui에서 사용하는 방식으로, 컴포넌트에 전달되는 props로 다양한 Tailwind 클래스를 조합할 때 조건부 렌더링과 클래스 충돌을 한 번에 처리할 수 있게 해줍니다.
예를 들어, 버튼 컴포넌트에서 variant, disabled, size 등 여러 상태에 따라 클래스를 다르게 적용해야 할 때, clsx로 조건부 조합 → tailwind-merge로 충돌 정리 과정을 거치면 코드가 깔끔하고 안전해집니다.
shadcn/ui의 cn 함수는 바로 이 패턴을 공식적으로 채택해, 모든 컴포넌트에서 일관된 클래스 관리가 가능하게 합니다.
// utils/cn.ts
import { clsx } from 'clsx';
import { twMerge } from 'tailwind-merge';
import type { ClassValue } from 'clsx';
/**
* Utility function to combine clsx and tailwind-merge
* @param inputs - The class values to merge
* @returns The merged class names
*/
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
// Button.tsx
import { cn } from '@/utils/cn';
export function Button({ disabled, variant }) {
return (
<button
className={cn(
'px-4 py-2 rounded',
variant === 'primary' && 'bg-blue-600 text-white',
variant === 'secondary' && 'bg-gray-200 text-black',
disabled && 'opacity-50 cursor-not-allowed'
)}
disabled={disabled}
>
버튼
</button>
);
}
이런 방식으로 클래스 관리를 하면, 다양한 조합과 상태 변화에도 스타일 충돌 없이 일관된 결과를 얻을 수 있습니다.
'Frontend > Tech Insight' 카테고리의 다른 글
| React Custom Hook은 어떻게 작동할까? (2) | 2025.08.10 |
|---|---|
| React에서 다국어(i18n) 처리하기 (With ICU format) (2) | 2025.06.08 |
| React에서 DOMPurify로 XSS 공격 방어하기 (0) | 2025.04.24 |
| Bun: 빠르고 혁신적인 JavaScript 런타임Bun이란? (0) | 2025.02.16 |
| npm이 설치하지 않은 패키지를 실행할 수 있는 이유 (0) | 2025.02.02 |
