cva Class Variance Authority
조건부 스타일링을 구조적으로 처리하기 위해 만들어진 CSS 클래스 관리 유틸리티 라이브러리로,
Tailwind CSS와 같이 유틸리티 클래스 기반의 스타일링을 사용할 때 유용하다.
cva는 컴포넌트의 여러 가지 형태와 상태를 정의하고, 이를 기반으로 동적인 CSS 클래스를 생성한다.
설치
npm install class-variance-authority
yarn add class-variance-authority
pnpm add class-variance-authority
API 설명
cva(baseClasses, options)baseClasses: 항상 적용되는 기본 클래스options:variants,compoundedVariants및defaultVariants을 정의하는 객체varaints: 상태나 변형에 따라 추가될 CSS 클래스 정의compoundedVariants: 개별 변형뿐 아니라 여러 변형의 상태가 특정 조건을 만족할 때만 적용할 CSS 클래스 정의defaultVariants: 기본적으로 적용될 변형을 지정
- 리턴:
options객체를 전달 받아 조건에 맞는 CSS 클래스 반환
const button = cva(baseClasses, options)
위와 같은 형태로 cva를 정의하며, button(options) 형태로 호출하여 사용한다.
즉, cva가 할당된 변수 button은 함수처럼 사용되며,
해당 함수에 전달되는 매개변수는 cva의 두 번째 매개변수인 options의 속성인 variants와 매칭되어 CSS 클래스가 특정된다.
// components/button.ts
import { cva } from "class-variance-authority";
const button = cva(["font-semibold", "border", "rounded"], {
variants: {
intent: {
primary: [
"bg-blue-500",
"text-white",
"border-transparent",
"hover:bg-blue-600",
],
// **or**
// primary: "bg-blue-500 text-white border-transparent hover:bg-blue-600",
secondary: [
"bg-white",
"text-gray-800",
"border-gray-400",
"hover:bg-gray-100",
],
},
size: {
small: ["text-sm", "py-1", "px-2"],
medium: ["text-base", "py-2", "px-4"],
},
},
compoundVariants: [
{
intent: "primary",
size: "medium",
class: "uppercase",
// **or** if you're a React.js user, `className` may feel more consistent:
// className: "uppercase"
},
],
defaultVariants: {
intent: "primary",
size: "medium",
},
});
button();
// => "font-semibold border rounded bg-blue-500 text-white border-transparent hover:bg-blue-600 text-base py-2 px-4 uppercase"
button({ intent: "secondary", size: "small" });
// => "font-semibold border rounded bg-white text-gray-800 border-gray-400 hover:bg-gray-100 text-sm py-1 px-2"
설명
button에 아무런 매개변수가 전달되지 않으면,
defaultVariants에 설정된 기본 상태 및 형태와variants와 매칭된 스타일이 반환된다.button에 전달된 매개변수{ intent: "secondary", size: "small" }는
각각variants.intent.secondary와variants.size.small에 해당하는 스타일을 가리킨다.
주의사항
- 조합 조건의 충돌
여러compoundedVariants가 동시에 만족하는 경우,
나중에 정의된 클래스가 덮어씌워질 수 있으므로, 조건을 신중히 작성해야 한다.
clsx vs. cva
clsx vs cva: 주요 기능 비교
| 기능 | clsx | cva |
|---|---|---|
| 주요 목적 | 조건부 CSS 클래스를 간결하게 작성 | 조건부 CSS 클래스 및 스타일 변형 관리 |
| 클래스 병합 | clsx로 다양한 조건의 클래스 병합 가능 |
base 속성으로 기본 클래스를 정의하고 변형을 조합 |
| 조건부 로직 | 단순한 조건부 클래스 적용 | variants 속성을 사용해 구조화된 조건부 스타일 적용 |
| 유연성 | CSS 클래스 병합에 특화 | 컴포넌트 스타일링을 구조적으로 관리 |
| 조합 가능성 | 배열, 객체, 문자열 등 다양한 입력 지원 | compoundVariants로 복잡한 조합 스타일링 가능 |
| 사용 사례 | 빠르게 조건부 클래스 병합이 필요할 때 | 상태, 변형, 조건 등을 기반으로 컴포넌트 스타일을 관리해야 할 때 |
clsx 예제
import clsx from 'clsx';
function Button({ isPrimary, isDisabled }) {
return (
<button
className={clsx(
'px-4 py-2 rounded', // 기본 스타일
isPrimary && 'bg-blue-500 text-white', // 조건부 스타일
isDisabled && 'opacity-50 cursor-not-allowed' // 비활성화 스타일
)}
>
Click Me
</button>
);
}
isPrimary가true이면bg-blue-500 text-white가 추가isDisabled가true이면opacity-50 cursor-not-allowed가 추가
cva 예제
import { cva } from 'class-variance-authority';
const buttonStyles = cva(
'px-4 py-2 rounded', // 기본 스타일
{
variants: {
intent: {
primary: 'bg-blue-500 text-white',
secondary: 'bg-gray-500 text-black',
},
disabled: {
true: 'opacity-50 cursor-not-allowed',
false: '',
},
},
defaultVariants: {
intent: 'primary',
disabled: false,
},
}
);
function Button({ intent, disabled }) {
return <button className={buttonStyles({ intent, disabled })}>Click Me</button>;
}
intent와disabled변형을 정의하여 구조화된 스타일 관리 가능defaultVariants로 기본 상태를 지정buttonStyles호출 시 객체 형태로 변형을 전달하여 동적으로 클래스 적용
clsx와 cva 비교
- clsx는 단순한 조건부 클래스 병합에 적합하며, 유연성과 간결함이 강점
- cva는 컴포넌트 스타일을 구조적으로 관리하고 변형을 쉽게 적용할 수 있어 더 복잡한 스타일링에 유리함
'Utility' 카테고리의 다른 글
| 텍스트 에디터 Tiptap 커스터마이징 (0) | 2025.01.19 |
|---|---|
| useWebSocket으로 웹소켓 구현하기 (1) | 2025.01.14 |
| 돌아가는 로딩 스피너 코드 (0) | 2024.12.23 |
| clsx (0) | 2024.12.13 |
| Tailwind Merge (0) | 2024.12.13 |