Tailwind css 클래스 합성하기 (feat. tailwind-merge)

Tailwind를 사용하다 보면 이미 만들어둔 컴포넌트를 재사용하지만 약간의 스타일 변경을 넣어주고 싶을 때가 있다. 그렇지만, 유틸 클래스의 한계로 만들어진 클래스들의 cascading에 따르기 때문에 개발자가 클래스를 넣어준 순서와 일치하지 않을 수 있다. tailwind-merge로 해결해보자.
2023.11.06

Tailwind를 사용하다 보면 이미 만들어둔 컴포넌트를 재사용하지만 약간의 스타일 변경을 넣어주고 싶을 때가 있다.

그렇지만, 유틸 클래스의 한계로 만들어진 클래스들의 cascading에 따르기 때문에 개발자가 클래스를 넣어준 순서와 일치하지 않을 수 있다.

.bg-special {
  --tw-bg-opacity: 1;
  background-color: rgb(147 129 255 / var(--tw-bg-opacity));
}

.bg-blue-500 {
  --tw-bg-opacity: 1;
  background-color: rgb(59 130 246 / var(--tw-bg-opacity));
}

function SpecailButton() {
  return <Button className="bg-special" />
}


fucntion Button({ className, ...props }) {
  // 뒤에 오는 클래스인 bg-special 이 적용되었으면 좋겠으나 css의 cascade 규칙상 bg-blue-500이 된다
  // 클래스가 생성되는 순서와 태그에 적용되는 순서와 일치하지 않을 수 있다
  return <button {...props} className=`bg-blue-500 ${className}`
}

그러면 후순위의 클래스들만 뭔가 파싱해서 적용되도록 해주면 되지 않을까..? 했는데 역시 있었다. 아래의 디스커션에서 출처를 얻었다.

https://github.com/tailwindlabs/tailwindcss/discussions/1446#discussioncomment-4459791

적용해보자.

적용

https://github.com/dcastil/tailwind-merge

// 설치
pnpm add -D tailwind-merge

예를 들어 아래와 같은 코드는 위의 예시 처럼 클래스 순서 문제를 야기한다.

      <button className="bg-blue-500 bg-gray-500 bg-green-500 bg-red-500">
        button
      </button>

bg-red-500이 적용되었으면 좋겠으나 실제로는 bg-gray-500 으로 적용되었다.

twMerge 함수로 변경해보자.

import { twMerge } from "tailwind-merge";

      <button
        className={twMerge("bg-blue-500 bg-gray-500 bg-green-500 bg-red-500")}
      >
        button
      </button>

실제 태그에 남는 클래스는 bg-red-500만 남았으며 생성되는 클래스도 bg-red-500만 남게 되었다.

어떻게 동작하는 걸까

Tailwind에서 제공하는 클래스들 목록에 대한 설정을 이 라이브러리가 가지고 있다.

https://github.com/dcastil/tailwind-merge/blob/main/src/lib/default-config.ts

예를 들어 간단한 float로 확인해보자.

            // https://github.com/dcastil/tailwind-merge/blob/main/src/lib/default-config.ts#L196
            float: [{ float: ['right', 'left', 'none'] }],

Tailwind에서 제공하는 right, left, none만 머지된다는 뜻이다. 실제로 해보자.

      <button className={twMerge("float-green float-right float-left")}>
        button
      </button>

위에는 float-green이라는 정의되지 않은 클래스를 넣었다. 과연 머지될까?

정답은 안된다.

위의 기본적으로 정의된 클래스들만 머지되기 때문이다.

그렇다면 기본 설정에서 추가로 설정하려면 어떻게 하고 싶을까? 기본 설정을 확장시킬 수 있는 함수를 제공한다.

https://github.com/dcastil/tailwind-merge/blob/main/docs/api-reference.md#extendtailwindmerge

만약 위와 같이 float-green 이라는 기본적인 tailwind 클래스가 아닌 경우 아래처럼 확장해서 사용하게 된다.

import { extendTailwindMerge } from "tailwind-merge";

  const ctwMerge = extendTailwindMerge({
    extend: {
      classGroups: {
        float: [{ float: ["green"] }],
      },
    },
  });

      <button className={ctwMerge("float-green float-right float-left")}>
        button
      </button>

그러면 아래처럼 잘 머지되게 된다.

그러면 매번 이래야되나..? 싶은데 bg-* 라던지 일반적으로 다양한 값이 올 수 있는 클래스들은 굳이 확장시키지 않더라도 무난하게 사용가능했다.

마무리

사실 이러한 클래스 머지처리는 tailwind-merge 말고도 twin.macro라는 라이브러리가 있는데, twin.macro는 바벨 매크로로 이러한 머지 문제를 해결할 수 있다.

심플하게 설정하고 싶다면 tailwind-merge도 좋은 것 같고, twin.macro로 바벨 매크로를 통해 좀 더 여러 설정을 하는 것도 방법일 수 있다.