이슈발생
사이드 프로젝트 팀에서 사용하는 디자인 시스템을 Tailwind config에 적용해주었습니다.
색상도 커스텀한 이름으로 적용해두었고요.
Turborepo + Next.js + Tailwind 스타터킷 프로젝트를 사용했기에, Tailwind 유틸함수를 자연스럽게 사용했는데요.
Tailwind에서는 텍스트 사이즈 조정을 할 때도, 텍스트 색상을 조정할 때도 유틸리티 클래스 이름이 text-
로 시작합니다.
Tailwind 유틸함수 cn
(내부에 tailwind-merge 및 clsx 사용)을 이용해 아래와 같이 스타일을 입힐 수 있는데요.
그러면, text-
로 시작한 클래스를 여러개 적용해두면 마지막 클래스만 남게 되는 현상이 발생했습니다.
// 커스텀-폰트크기, 커스텀-색상
className={cn('text-button-md text-gray-500')}
// 커스텀-색상, 폰트크기
className={cn('text-gray-500 text-20')}
// 커스텀-색상, 커스텀-폰트크기
className={cn('text-gray-500 text-button-md')}
Tailwind-merge는 무슨 역할을 하는 것일까?
tailwind-merge에서 제공하는 twMerge
함수에 의해 이러한 현상이 발생하는 것인데요.
그럼, twMerge
는 무슨 역할을 하는 것일까요?
twMerge
를 왜 사용해야 하는 지 알아야할 것 같습니다.
유틸리티 클래스(bootstrap, tailwind)를 사용하면, 만들어진 클래스를 가져다 쓰면 되니 편리한데요.
유틸리티 클래스의 단점이 존재합니다.
tailwind는 빌드 후에 CSS 파일을 생성하는 데, 여기에 우리가 사용할 유틸리티 클래스들이 정의되어 있습니다.
패딩에 대한 클래스 - p, px, pl
등도 선언되어 있습니다.
CSS에서 클래스 선택자로 동일한 CSS 속성을 사용할 경우, 나중에 선언된 클래스 선택자가 적용되는 데요. (cascading)
유틸리티 클래스에서도 마찬가지입니다.
나중에 적용한 클래스(오른쪽에 적용한 클래스)가 앞의 클래스의 CSS 속성을 덮어쓸 것이라 생각했으나 의도대로 동작하지 않습니다.
// 패딩가로가 8로 바뀔 것으로 기대했으나 4로 유지됨
className="px-4 p-8"
// 패딩왼쪽이 8로 바뀔 것으로 기대했으나 4로 유지됨
className="pl-4 px-8"
개발자 도구를 열어서, 유틸리티 클래스 파일을 살펴보면 그 원인을 알게 됩니다!px
클래스 선택자들은 p
클래스 선택자들보다 뒤에,pl
클래스 선택자들은 px
클래스 선택자들보다 뒤에 위치해 있기 때문입니다.
클래스 선택자 순서가 예상한 것과 다르게 위치해 있는데요.twMerge
는 클래스 선택자들의 CSS 속성이 겹칠 경우 마지막 클래스 선택자만 남겨두는 역할을 수행해줍니다.
// p-8만 남음
className={twMerge("px-4 p-8")}
// px-8만 남은
className="px-8"
twMerge가 클래스를 삭제하는 원리는 무엇일까?
tailwind-merge 라이브러리에서는 Class group(클래스 그룹) 과 Conflicting Class group (충돌하는 클래스 그룹) 이라는 개념이 등장합니다.
Class group
Class group은 같은 CSS 속성을 사용하는 유틸리티 클래스들을 정의한 그룹 (문자열 배열) 입니다.
const positionClassGroup = ['static', 'fixed', 'absolute' ... ]
위처럼 position
CSS 속성을 변경하는 클래스들을 묶어놓는 것인데요.
이런 Class group들은 tailwind-merge config에 기본적으로 적용이 되어 있습니다. (default config)
twMerge('static sticky relative') // 'relative'
어떤 유틸리티 클래스에는 접두어가 붙는 경우가 있습니다. ex) overflow-*
그런 경우에는 아래와 같이 Class group을 적용해줄 수 있습니다.
// 접두어(overflow)가 객체의 키가 됨.
const overflowClassGroup = [{ overflow: ['auto', 'hidden', 'visible', ...] }];
twMerge('overflow-auto overflow-hidden overflow-visible'); // 'overflow-visible'
일일히 나열하기 어렵다면, validator 함수를 이용해서 조건에 맞는 클래스들을 Class group에 넣을 수 있습니다.
const isUnder201 = (value: string) => Number(value) <= 200;
const fontSizeGroup = [{ text: [isUnder201] }];
Conflicting Class group (충돌하는 클래스 그룹)
Class group를 이용해 어떤 클래스를 제거해야할 지 판단할 수 있으나, 좀 더 복잡한 케이스가 존재합니다.
'pr-4 px-3' // pr-4를 없애도 문제없음
'px-3 pr-4' // px-3을 없애면 패딩왼쪽 3도 제거됨
위의 경우처럼 같은 px-3
, pr-4
를 사용하더라도 순서에 따라 스타일을 변경하는 의도가 달라질 수 있습니다.
패딩 CSS 속성을 변경한다고 앞의 클래스 선택자를 제거하면 문제가 됩니다.
이런 경우에 대처하기 위해 Conflicting Class group 이라는 것이 존재합니다.
const conflictingClassGroups = {
px: ['pr', 'pl'],
...
}
위의 경우처럼 tailwind-merge config에 적용할 수 있습니다.
객체 키에는 Class group ID가 들어갑니다. (쉽게 생각해서 유틸리티 클래스 접두어)
충돌이 될만한 클래스를 넣어주고, 충돌되는 클래스들이 앞에 존재할 경우 제거해줍니다.
twMerge('pr-4 px-3'); // 'px-3'
twMerge('px-3 pr-4'); // 'px-3 pr-4'
그럼, 위의 경우처럼 충돌되지 않는 판단하는 경우 앞의 클래스들을 제거하지 않습니다.
자, 이제 문제를 해결해보기
어느 정도, 원리를 이해했으니 문제를 해결해봅시다.
위와 같은 이유로 tailwind-merge에서는 config에 추가적인 정보를 제공하지 않으면 충돌된다고 판단되는 클래스 들은 제거해버립니다.
text-button-md text-gray-500 text-20
, 이 클래스들은 text-
로 시작하고 커스텀 클래스도 있는데요.
tailwind-merge에서는 아직 모르니까 먼저 선언된 클래스들을 제거하는 것입니다.
tailwind-merge에서는 twMerge
대신 extendTailwindMerge
를 사용할 수 있는데요.extendTailwindMerge
에서 앞에서 설명한 classGroup를 선언할 수 있고, 커스텀한 클래스들을 extend
할 수 있습니다.
import { extendTailwindMerge } from 'tailwind-merge';
// twMerge 대신 extendTailwindMerge 사용
const customTwMerge = extendTailwindMerge({
extend: {
theme: {
colors: Object.entries(sharedConfig.theme?.colors || {}).map(([k, v]: [string, Record<string, string>]) => ({
[k]: Object.keys(v),
})),
},
classGroups: {
'font-size': [
...Object.keys(sharedConfig.theme?.fontSize || {}).map((key) => `text-${key}`),
{ text: [(value: string) => Number(value) <= 200] },
],
},
},
});
// cn 유틸 함수를 수정
export const cn = (...inputs: ClassValue[]) => {
return customTwMerge(clsx(inputs));
// return twMerge(clsx(inputs));
};
1) extend.theme.colors
에 형식에 맞게 커스텀 색상 클래스를 넣어주었습니다.
2) extend.classGroups\['font-size'\]
에 커스텀 폰트 클래스를 넣어주었습니다.
tailwind-merge에 위와 같은 정보를 알려주면,text-[커스텀 컬러 클래스]
와 text-[커스텀 폰트 클래스]
는 다른 ClassGroup 인 것을 알게 되어 클래스 충돌을 막을 수 있어서 의도한대로 잘 동작하게 됩니다.
참고
https://github.com/dcastil/tailwind-merge/issues/368
https://github.com/dcastil/tailwind-merge/blob/v2.3.0/docs/configuration.md
'Web' 카테고리의 다른 글
카카오톡 공유하기 (Javascript) 를 이용해 원하는 정보 전달하기 (0) | 2024.08.14 |
---|---|
[Web] Discord 채팅채널에 Github Action을 이용해서 Vercel 배포 알림 보내기 (Vercel Hobby Plan) (0) | 2024.05.08 |
[Web] Storybook를 시작해보자 (with Typescript, Next.js) (0) | 2023.07.07 |
[Web] Git 원격저장소의 브랜치 추적하기 (0) | 2023.05.24 |
[Web] Gitlab-runner 명령어 몇 가지 정리해보기 (0) | 2023.04.29 |