본문 바로가기
Web

tailwind-merge로 클래스 충돌 해결하기

by 너리 Neori 2024. 4. 11.

TL;DR

tailwind-merge, twin.macro와 같은 NPM 패키지를 활용하면 하나의 요소에 같은 속성을 정의하는 className이 두 개 이상일 경우, 가장 마지막에 위치한 클래스가 최종적으로 적용되게 할 수 있습니다.

 

 

문제 상황

저는 요즘 React, TailwindCSS, TypeScript를 이용한 UI 공통 컴포넌트 라이브러리를 만들고 있는데요.

 

BaseButton이라는 기본 Button UI 컴포넌트를 만든 후에 BaseButton 컴포넌트를 사용하는 기본 Chip 컴포넌트를 만들었습니다. 스토리북으로 문서화하기 위해 스토리를 작성하던 중에 예상한 대로 동작하지 않아서 개발자 도구를 함께 열어보고 확인해보았습니다.

// BaseChip.tsx
interface Props {
  children?: React.ReactNode;
  selected?: boolean;
  onClick?: React.MouseEventHandler<HTMLButtonElement>;
  styleClass?: {
    default: string;
    selected?: string;
  };
}

export default function BaseChip({
  children,
  selected,
  styleClass,
  ...rest
}: Props) {
  return (
    <BaseButton
      styleClass={`border rounded-3xl text-sm px-4 py-2 ${styleClass.default} ${selected ? styleClass?.selected : ''}`}
      {...rest}
    >
      {children}
    </BaseButton>
  );
}
// BaseChip.stories.tsx

// ..코드 생략
<BaseChip
  onClick={handleClick}
  styleClass={{
    default: 'border-[#919191] bg-white text-[#919191]',
    selected: 'border-black bg-black text-white',
  }}
  selected={selected}
  {...args}
>
  {children}
</BaseChip>

 

BaseButton의 styleClass로 props가 잘 전달되어 DOM 상에서도 클래스명이 순서대로 반영되었고, 나중에 선언한 .bg-black이 적용될 거라고 예상했었습니다. 하지만 실제로는 나중에 선언한 .bg-black이 아닌 .bg-white 클래스가 적용되고 있었습니다.

 

이는 실제 TailwindCSS 내부의 stylesheet에서 .bg-white가 .bg-black보다 나중에 선언되어 있기 때문이라고 추측됩니다(실제 내부 코드에서도 black이 white보다 항상 먼저 선언되어 있음). CSS가 결국 cascading 언어이기 때문에 나중에 선언되어 있는 코드를 기준으로 최종 적용되는 것인데요.

 

검정색 배경의 버튼을 원했는데..!

 

해결 방안

찾아보니 실제로 Tailwind CSS 저장소에서도 관련 논의가 예전부터 있었습니다.

[Feature Proposal] CSS class override utility for component composition. · tailwindlabs tailwindcss · Discussion #1446

 

[Feature Proposal] CSS class override utility for component composition. · tailwindlabs tailwindcss · Discussion #1446

First, ❤️ Tailwind!!! Problem Working within the scope of a large FE application (my use case is React with hundreds of components), it's common to create styled components that are composable. Rea...

github.com

 

스타일 적용 과정에서 발생하는 클래스 간 충돌과 의도치 않은 우선순위의 적용 문제를 해결하기 위해 tailwind-merge, twin.macro 등의 라이브러리를 활용할 수 있습니다.

 

라이브러리 중 twin.macro와 tailwind-merge를 비교했을 때, tailwind-merge가 더 많이 쓰이고 비교적 관리가 잘 되고 있어 채택하게 되었습니다.

npm trends

 

 

다음과 같이 tailwind-merge의 twMerge를 적용해보았습니다.

DOM 상에서는 twMerge() 메서드의 내부 로직에 의해 실제 적용될 클래스만 필터링되어 반영됩니다.

 

자세한 사용 방법은 tailwind-merge의 README.md에 설명되어 있으니 참고해보면 좋을 것 같습니다. 사용 후에 이전보다 코드 자체도 더 명확해진 것을 확인할 수 있었습니다.

// installation
npm install tailwind-merge --save-dev
// BaseChip.tsx
import { twMerge } from 'tailwind-merge';

export default function BaseChip({
  children,
  selected,
  styleClass,
  ...rest
}: Props) {
  return (
    <BaseButton
      styleClass={twMerge(
        'border rounded-3xl text-sm px-4 py-2',
        styleClass.default,
        selected && styleClass?.selected,
      )}
      {...rest}
    >
      {children}
    </BaseButton>
  );
}

 

배경색이 잘 적용되었다!

 

 

 

 

 

예상대로 TailwindCSS의 클래스들이 잘 적용되지 않는다면 tailwind-merge처럼 유연성을 더해주는 도구를 활용해봐도 좋을 것 같습니다. 

 

 

혹시 TailwindCSS와 함께 사용하면 좋을 유용한 도구를 알고 계신다면 공유 부탁드립니다 :)

글 읽어주셔서 감사합니다!