Tailwind CSS 에서 동적으로 클래스 할당하기

윤상준·2022년 6월 15일
23

React

목록 보기
1/4
post-thumbnail
post-custom-banner

개요

좋아하는 게임인 Clash of Clans API를 사용하여 OP.GG 같은 사이트를 만들어보는 개인 프로젝트를 진행 중이다. (Tailwind CSS는 정말 신세계다.)

아직 시작한지 얼마 안됐지만 이슈가 하나 있어 기록하려고 한다.

참고로 Tailwind CSS는 이런식으로 className 만으로도 CSS를 적용할 수 있어 매우 편리하다.

<div className="flex justify-between items-center w-full px-6 py-2">

자세한 내용은 공식 문서 에서 확인해보자.

이런 뷰를 만들고싶었다.

Troops, Spells, Heroes 섹션을 ImageCard 컴포넌트로 제작하고 있고, 각 컴포넌트 안에 있는 사진들을 grid, grid-template-columns 로 정렬하려고 했다.

...
<ImageCards title={"Heroes"} villiage={hall} sort={"heroes"} gridCols={4} />
<ImageCards title={"Troops"} villiage={hall} sort={"troops"} gridCols={8} />
<ImageCards title={"Spells"} villiage={hall} sort={"spells"} gridCols={8} />
...

Tailwind 에서는 grid-template-columns 를 이런 식으로 적용할 수 있다.

따라서 나는 gridCols 를 Props로 전달받아서 동적으로 할당하고자 했다.

실패

템플릿 리터럴을 사용해봤다.

...
const ImageCards = ({ ... gridCols ... } : { ... IProps ... }) => {
  <div className={`grid${gridCols && ` grid-cols-${gridCols}`} gap-3`}>
...

이런 식으로 gridCols가 있으면 grid-cols-{props.gridCols} 가 생성되는 형식이다.

신나는 마음으로 실행시켜봤다.


??

아니 찍혀있는데?

styleSheet에는 적용이 되지 않는다!

구글링

템플릿 리터럴을 사용해서 동적으로 할당하는 방식이 문제가 될 거라고는 전혀 생각 못했다.
그래서 애꿎은 멀쩡한 것들만 건들다가 설마하는 심정으로 구글링을 해봤다.

구글을 돌아다니던 중 나와 비슷한 문제를 겪은 분을 뵙게 되었다.

Next.js and Tailwind can't use template string for applying classes problem - reddit

굉장히 화가 많이 나셨다.

요약하자면 color 라는 변수를 사용해서 'bg-' + color + '-500' 식으로 배경 색을 적용하고 싶은데 안되서 화가 많이 나셨다고 한다.

댓글을 읽어보다가 원인과 해결법을 찾았다.

원인

Tailwind는 사용되지 않는 CSS는 제거합니다. 따라서 당신의 코드가 컴파일링 되는 시점에서, Tailwind는 동적 할당을 인식하지 못하고 stylesheet에서 지워버릴 겁니다.

실제로 공식 문서에도 tailWind는 동적으로 생성된 클래스는 인식하지 않는다 라고 강조하고 있다.

해결법

Safelisting classes

TailwindCSS - SafeListing Classes

tailwind.config.js에는 SafeList 기능이 있다. 이름에서 알 수 있듯이 SafeList에 들어가있는 클래스는 purge (제거) 되지 않는 것 같다.

마찬가지로 정규 표현식 또는 특정 변수를 포함하는 클래스 들을 SafeList 에 할당할 수 있다.

// tailwind.config.js
module.exports = {
  content: [
    './pages/**/*.{html,js}',
    './components/**/*.{html,js}',
  ],
  safelist: [
    'text-2xl',
    'text-3xl',
    {
      pattern: /bg-(red|green|blue)-(100|200|300)/,
      variants: ['lg', 'hover', 'focus', 'lg:hover'],
    },
  ],
  // ...
}

하지만 내가 필요한 상황과 공식 문서에서 얘기한 useCase 와는 거리가 좀 있는 것 같아서 패스했다.

객체 활용

내가 선택한 방법이다.

항상 완성된 하나의 클래스를 사용하라는 공식 문서의 권고를 따라서

gridCols를 key 로 하고, value는 해당 gridCols를 포함한 전체 CSS 코드 (이게 핵심)를 저장했다.

type gridColumnsType = {
  [key: number]: string;
};

const gridColumns: gridColumnsType = {
  0: "grid gap-3",
  4: "grid grid-cols-4 gap-3",
  8: "grid grid-cols-8 gap-3",
};

컴포넌트에서는 다음과 같이 불러왔다. (0 은 디폴트 값)

...
const ImageCards = ({ ... gridCols ... } : { ... IProps ... }) => {
  <div className={gridColumns[gridCols ?? 0]}>
...

성공!

추가로 구글을 좀 더 돌아다녀보니 객체를 더 세밀하게 사용하는 케이스도 찾을 수 있었다.

Dynamically build classnames in TailwindCss - stack overflow

const buttonConfig = {
  // Colors
  primary: {
    bgColor: 'bg-primary-500',
    color: 'text-white',
    outline:
      'border-primary-500 text-primary-500 bg-opacity-0 hover:bg-opacity-10',
  },
  secondary: {
    bgColor: 'bg-secondary-500',
    color: 'text-white',
    outline:
      'border-secondary-500 text-secondary-500 bg-opacity-0 hover:bg-opacity-10',
  },

  // Sizes
  small: 'px-3 py-2',
  medium: 'px-4 py-2',
  large: 'px-5 py-2',
};
  <motion.button
    whileTap={{ scale: 0.98 }}
    className={`
    rounded-lg font-bold transition-all duration-100 border-2 focus:outline-none
    ${buttonConfig[size]}
    ${outlined && buttonConfig[color].outline}
    ${buttonConfig[color].bgColor} ${buttonConfig[color].color}`}
    onClick={onClick}
    type="button"
    tabIndex={0}
  >
    {children}
  </motion.button>
profile
하고싶은건 많은데 시간이 없다!
post-custom-banner

3개의 댓글

comment-user-thumbnail
2024년 3월 16일

덕분에 해결합니다! 😀

답글 달기
comment-user-thumbnail
2024년 5월 9일

덕분에 살았습니다 ㅠㅠ

답글 달기