Style/Libraries

Tailwind CSS 잘 활용하기 with Clxs, CVA, twMerge

iam102 2024. 3. 31. 14:45

최근 팀 내에서 Tailwind CSS Class를 사용할 때 좀 더 효율성 있게 작성하는 방식에 대해 논의하게 되었습니다. 기존 작업 중인 프로젝트에서는 Styled Component와 Tailwind CSS를 같이 쓰는 방식을 채택하여 사용 중인데 새롭게 들어가는 프로젝트에선 clsx, class-variance-authority, tailwind-merge를 조합하여 사용하는 방식을 사용해 보려고 합니다.

 

필요 라이브러리

※ React에서 Tailwind CSS 설정이 되어 있는 환경을 가정합니다.

clsx는 조건부 className 처리를 위해 사용되며 라이브러리 크기가 매우 작은 장점을 가지고 있습니다.

import { clsx } from 'clsx';

clsx('w-full', true && 'h-full', { 'px-4': true, 'py-2': false }, [
  'flex',
  'items-center',
]);
// => w-full h-full px-4 flex items-center

 

cva(class-variance-authority)는 props에 따른 className 처리를 위해 사용됩니다.

import { cva } from 'class-variance-authority';

export const ComponentVariants = cva(
  `
  flex items-center justify-center 
  `,
  {
    variants: {
      variant: {
        default: '',
        full: 'w-full h-full',
      },
    },
    defaultVariants: {
      variant: 'default',
    },
  },
);

// <div className={ComponentVariants({ variant })} />

 

tailwind-mergeclassName 충돌을 막아주어 겹치는 className의 경우 마지막에 선언된 className 기준 오버라이드 처리됩니다.

import { twMerge } from 'tailwind-merge'

twMerge('px-2 py-1 bg-red-600 p-3 bg-grey-800')
// => p-3 bg-grey-800

 

npm install clsx class-variance-authority tailwind-merge --dev
# or
yarn add clsx class-variance-authority tailwind-merge --dev

 

util 함수 작성

clsx, class-variance-authority, tailwind-merge를 조합하여 사용하기 위해 유틸함수를 만듭니다.

// utils/style.ts
import { ClassValue, clsx } from 'clsx';
import { twMerge } from 'tailwind-merge';

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

clsx와 tailwind-merge를 통해 조건부 className과 className 충돌을 방지하는 유틸함수 입니다.

 

cva와 작성된 유틸함수 cn을 활용한 Button 예제입니다.

// components/Button.tsx
import { cva, VariantProps } from 'class-variance-authority';

import { cn } from '@/utils/style';

export const ButtonVariants = cva(
  `
  flex items-center justify-center 
  bg-gray-950 text-white
  rounded-sm text-base font-medium
  `,
  {
    variants: {
      variant: {
        default: 'shadow-none',
        grey: 'bg-gray-150 text-gray-950',
        red: 'bg-red-600',
      },
      size: {
        default: 'px-2 py-1',
        md: 'px-4 py-2',
        lg: 'px-6 py-3 text-lg',
        xl: 'px-8 py-4 text-xl',
      },
    },
    defaultVariants: {
      variant: 'default',
      size: 'default',
    },
  },
);

interface ButtonProps extends VariantProps<typeof ButtonVariants> {
  className?: string;
  children?: React.ReactNode;
}

function Button({ variant, size, className, children }: ButtonProps) {
  return (
    <button className={cn(ButtonVariants({ variant, size, className }))}>
      {children && children}
    </button>
  );
}

export default Button;

 

cva에서는 선언된 variants 외에도 className을 추가로 받을 수 있습니다. 추가할 className이 없다면 이 부분은 생략하셔도 됩니다.

 

작성된 Button Component 사용 예제입니다.

import Button from '@/components/Button';

function Component() {
  return (
    <div className="w-full h-full p-10 space-y-2">
      <Button size="md">btn1</Button>
      <Button className="text-red-600" variant="grey" size="lg">
        btn2
      </Button>
      <Button variant="red" size="xl">
        btn3
      </Button>
    </div>
  );
}

export default Component;

 

VSCode 추가 설정

VSCode에서 Tailwind CSS IntelliSense extension을 사용할 경우 cva 내부에서 작동을 안 하는 것을 확인할 수 있습니다. cva 내부에서 작동할 수 있도록 setting.json에 해당 항목을 추가합니다.

{
 ...
 "tailwindCSS.experimental.classRegex": [
    ["cva\\(([^)]*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]"]
  ]
}

 

마무리

Tailwind CSS와 같이 사용할 수 있는 라이브러리 덕분에 Tailwind CSS를 잘 활용할 수 있는 방법이 계속 나오고 있습니다. 이전에는 Styled Components와 Tailwind CSS를 같이 쓰는 방식을 포스팅했었는데 작업 환경에 맞게 여러 방식을 선택하여 사용하면 되겠습니다. 단순히 Tailwind CSS를 쓰는 것에 그치지 않고 좀 더 활용할 수 있는 방안을 계속 찾아봐야겠습니다.


참고 자료

https://medium.com/@gorkemkaramolla/react-tailwind-reuseable-and-customizable-components-with-cva-clsx-and-tailwindmerge-combo-guide-c3756bdbbf16

https://xionwcfm.tistory.com/328

https://xionwcfm.tistory.com/325