Tailwind CSS 잘 활용하기

March 10, 2023·6 min read
Tailwind CSS 잘 활용하기

최근에는 SSR이나 SSG와 같은 서버에서 먼저 빌드해야 하는 경우가 많아졌습니다. 이에 따라 CSS-in-JS와 같은 스타일 적용보다는 Tailwind CSS를 사용하면 다른 설정 없이 간편하게 사용할 수 있으며, 다양한 스타일에 대한 Preset도 존재하기 때문에 많이 사용하고 있습니다. 따라서, 제가 Tailwind CSS를 사용하면서 얻은 팁들을 공유하고자 합니다.

중복된 스타일에 대한 처리

하나의 클래스에 동일한 스타일에 관한 클래스가 추가된 경우, 빌드 결과에 따라서 달라지게 됩니다.

<span class="text-blue-700 text-red-700"></span>
 
<span class="text-red-700 text-blue-700"></span>

위와 같이, 하나의 클래스에 text-blue-700text-red-700 이 같이 있는 경우, 클래스 순서와 상관 없이 text-red-700 만이 적용됩니다. tailwindplay에서 확인하기

그 이유는, 컴파일 이후에 css 파일이 생성되었을 때, 아래와 같이 text-red-700이 뒤에 위치해 있기 때문입니다.

output.css
.text-blue-700 {
  --tw-text-opacity: 1;
  color: rgb(29 78 216 / var(--tw-text-opacity));
}
 
.text-red-700 {
  --tw-text-opacity: 1;
  color: rgb(185 28 28 / var(--tw-text-opacity));
}

이러한 문제를 어떠한 방식으로 해결할 수 있을까요?

Tailwind CSS IntelliSense

이러한 문제점은 Tailwind CSS IntelliSense와 같은 IDE의 Extension을 활용하면, 문제가 발생하기 전에 찾을 수 있습니다.

중복된 스타일이 있거나, 잘못된 class 이름을 사용하는 것들을 알려주는 Linting 기능과, 자동완성 기능 및 해당 class가 실제로 어떤 식으로 빌드되는지 한 눈에 볼 수 있기 때문에 개발 과정에서 알 수 있습니다. 그래서, tailwindcss를 사용한다면 꼭 사용하면 좋을 것 같습니다.

Tailwind CSS IntelliSense Screenshot
Tailwind CSS IntelliSense Screenshot

tailwind-merge

tailwind-merge를 사용하여 중복된 스타일에 대한 충돌을 방지하거나, 기존 스타일을 덮어 쓸 수 있습니다.

import { twMerge } from 'tailwind-merge';
 
twMerge('px-2 py-1 bg-red hover:bg-dark-red', 'p-3 bg-[#B91C1C]');
// → 'hover:bg-dark-red p-3 bg-[#B91C1C]'

위의 예제처럼, 앞의 클래스가 아닌 뒤의 인자의 스타일이 적용되는 것을 알 수 있습니다. 이 처럼, 미리 선언해둔 class에 새로운 스타일을 추가하려고 할 때 유용합니다.

가독성 높이기

class 만으로 스타일을 정의하다 보니 아래와 같이 상당히 class 의 이름이 길어지게 되고, 가독성이 떨어질 수 있습니다. 이런 문제를 어떻게 해결할 수 있을까요?

<div
  class="bg-blue-600 dark:bg-blue-400 dark:hover:bg-blue-300 hover:bg-purple-700 flex h-10 items-center justify-center gap-1.5 rounded-md border border-transparent px-4 text-center text-sm font-bold text-white transition duration-150 md:h-12 md:text-lg"
></div>

그룹화

저는 class가 너무 길어지게되면, 아래와 같이 관련된 class들을 그룹화하여 사용합니다. 이를 통해 스타일이 한눈에 보이고 수정 및 관리가 쉬워집니다.

import clsx from 'clsx';
 
export const Button = () => {
  return (
    <button
      className={clsx(
        // Group 1: 기본 유틸리티
        'flex h-10 items-center justify-center gap-1.5 rounded-md border px-4 text-center text-sm font-bold transition duration-150',
        // Group 2: 색상 관련 유틸리티
        'border-transparent bg-blue-600 text-white',
        // Group 3: Breakpoint 관련 유틸리티
        'md:h-12 md:text-lg',
        // Group 4: Darkmode 관련 유틸리티
        'dark:bg-blue-400 dark:hover:bg-blue-300',
        // Group 5: 다른 유틸리티 (:hover, :first, ...)
        'hover:bg-blue-700',
      )}
    ></button>
  );
};

cva를 통한 variant 추가하기

cva는 기존의 StitchesVanila-Extract에서 지원하던, variant를 tailwindcss에서 적용할 수 있도록 든 유틸리티 패키지입니다.

이를 통하여, 반복되는 스타일이나, 디자인 시스템을 만들 수 있습니다.

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"

위는 공식문서의 예제인데 이것을 보면 간단하게, 각 variant에 대한 class를 미리 정의해두고, 인자로 받은 해당 variant들에 대한 class를 반환합니다.

button.tsx
import type { VariantProps } from 'class-variance-authority';
import clsx from 'clsx';
 
type ButtonProps = React.ComponentPropsWithoutRef<'button'> & VariantProps<typeof button>;
 
export const Button = ({ className, intent, size, children, ...props }: ButtonProps) => {
  return (
    <button className={clsx(className, button({ intent, size }))} {...props}>
      {children}
    </button>
  );
};

React에서는 위와 같이 사용하여, 다양한 스타일의 Button을 생성할 수 있는 컴포넌트를 만들 수 있습니다.

참조