어제 오늘 내일

clsx로 React 조건부 클래스 깔끔하게 관리하기 본문

IT/React

clsx로 React 조건부 클래스 깔끔하게 관리하기

hi.anna 2026. 8. 7. 14:51

1. 들어가며: 어떤 문제를 해결하는가

React 컴포넌트를 만들다 보면 props나 state 값에 따라 className을 다르게 줘야 하는 경우가 정말 많습니다. 처음에는 템플릿 리터럴이나 삼항 연산자로 충분해 보이지만, 조건이 두세 개만 넘어가도 코드가 금방 지저분해집니다.

// 조건이 늘어날수록 읽기 힘들어지는 예
function Button({ variant, disabled, fullWidth }) {
  return (
    <button
      className={
        'px-4 py-2 rounded-lg font-medium ' +
        (variant === 'primary' ? 'bg-blue-500 text-white ' : '') +
        (variant === 'secondary' ? 'bg-gray-200 text-gray-800 ' : '') +
        (disabled ? 'opacity-50 cursor-not-allowed ' : '') +
        (fullWidth ? 'w-full' : '')
      }
    >
      버튼
    </button>
  )
}

문자열 연결 사이에 공백을 빠뜨리거나, 조건이 false일 때 빈 문자열을 제대로 처리하는지 신경 쓰다 보면 정작 스타일 로직보다 문자열 조립에 더 많은 신경을 쓰게 됩니다. clsx는 바로 이 문제를 해결하기 위한, 아주 작지만 실용적인 유틸리티 라이브러리입니다.

2. 설치하기

npm install clsx

번들 크기가 300바이트 남짓으로 매우 작아서 부담 없이 추가할 수 있습니다.

3. 기본 사용법

clsx는 다양한 타입의 인자를 받아서, 유효한(truthy) 값만 골라 공백으로 이어 붙인 하나의 문자열을 반환합니다.

문자열과 조건식

import clsx from 'clsx'

clsx('base', true && 'active', false && 'hidden')
// → 'base active'

&& 연산자를 활용하면 조건이 false일 때 자동으로 무시됩니다. undefined, null, 0, '' 같은 falsy 값도 마찬가지로 걸러집니다.

객체 문법

조건이 여러 개일 때는 객체 형태가 더 읽기 좋습니다. key가 클래스명, value가 조건(boolean)입니다.

clsx({
  'bg-blue-500': variant === 'primary',
  'bg-gray-200': variant === 'secondary',
  'opacity-50 cursor-not-allowed': disabled,
})

배열 문법

배열로 감싸서 여러 값을 한 번에 전달할 수도 있고, 문자열/객체/배열을 자유롭게 섞어 쓸 수도 있습니다.

clsx(['base', 'flex'], { 'w-full': fullWidth }, disabled && 'opacity-50')

4. React 컴포넌트에서 활용하기

앞서 예로 든 지저분한 버튼 컴포넌트를 clsx로 다시 작성하면 이렇게 정리됩니다.

import clsx from 'clsx'

function Button({ variant, disabled, fullWidth, children }) {
  return (
    <button
      className={clsx(
        'px-4 py-2 rounded-lg font-medium transition',
        variant === 'primary' && 'bg-blue-500 text-white hover:bg-blue-600',
        variant === 'secondary' && 'bg-gray-200 text-gray-800 hover:bg-gray-300',
        disabled && 'opacity-50 cursor-not-allowed',
        fullWidth && 'w-full'
      )}
      disabled={disabled}
    >
      {children}
    </button>
  )
}

조건 하나하나가 한 줄씩 명확하게 나열되기 때문에, 어떤 상황에 어떤 클래스가 적용되는지 한눈에 파악할 수 있습니다. 조건을 추가하거나 삭제할 때도 해당 줄만 건드리면 되어 유지보수가 쉬워집니다.

5. classnames 라이브러리와의 차이

clsx 이전부터 널리 쓰이던 비슷한 라이브러리로 classnames가 있습니다. API는 거의 동일해서 대부분의 코드는 import문만 바꾸면 그대로 호환됩니다.

// classnames
import classNames from 'classnames'
classNames('base', { active: true })

// clsx
import clsx from 'clsx'
clsx('base', { active: true })

가장 큰 차이는 번들 크기와 실행 속도입니다. clsx는 기능을 최소한으로 좁히는 대신 훨씬 가볍고 빠르게 동작하도록 설계되어 있어서, 신규 프로젝트라면 clsx가 더 널리 권장되는 선택지입니다.

6. tailwind-merge와 함께 쓰기

clsx는 조건에 따라 클래스를 조합하는 역할만 할 뿐, Tailwind 클래스끼리의 충돌(예: p-4p-8이 동시에 존재하는 경우)은 해결해주지 않습니다. 이 부분은 tailwind-merge의 역할이라, 둘을 함께 묶어 쓰는 cn 헬퍼 함수 패턴이 실무에서 널리 쓰입니다.

import clsx from 'clsx'
import { twMerge } from 'tailwind-merge'

function cn(...inputs) {
  return twMerge(clsx(...inputs))
}
function Card({ className, children }) {
  return (
    <div className={cn('p-4 bg-white rounded-lg shadow-md', className)}>
      {children}
    </div>
  )
}

clsx가 조건부 조합을 만들고, twMerge가 그 결과에서 충돌하는 클래스를 정리해주는 구조입니다. 이 조합은 shadcn/ui를 비롯한 많은 컴포넌트 라이브러리에서 표준처럼 사용되고 있습니다.

7. 마무리

clsx는 크기도 작고 배우는 데 5분도 걸리지 않는 라이브러리지만, React + Tailwind 조합으로 개발할 때 조건부 스타일링 코드를 눈에 띄게 깔끔하게 만들어줍니다. 문자열 템플릿이나 중첩 삼항 연산자로 className을 조립하고 있다면, clsx 도입만으로도 가독성이 크게 개선될 겁니다. 여기에 tailwind-merge까지 더한 cn 헬퍼 패턴을 프로젝트 초반에 만들어두면, 이후 컴포넌트를 작성할 때마다 매번 재사용할 수 있습니다.

반응형
Comments