[번역] Tailwind CSS에서 혼란을 방지하기 위한 5가지 모범 사례

강엽이·2023년 10월 21일
73
post-thumbnail

원문 : https://evilmartians.com/chronicles/5-best-practices-for-preventing-chaos-in-tailwind-css
원문 저자 : Nina Torgunakova, Travis Turner

Tailwind CSS로 작업하는 것은 매우 빠르고 쉽습니다(Tailwind CSS가 널리 인정받은 이유이기도 합니다). HTML에 다양한 클래스 목록을 붙여넣기만 하면 즉시 매력적인 인터페이스가 완성됩니다! 하지만 애플리케이션이 성장함에 따라 클래스 목록도 늘어납니다. 그러다 어느 날 코드를 이해할 수 없다는 것을 깨닫고 애플리케이션의 구조와 마법 변수에 혼란스러워지고 작업이 힘들어집니다. 이 글에서는 이러한 시나리오를 피하고, Tailwind CSS를 사용할 때 유용하게 사용할 수 있는 몇 가지 모범 사례를 공유하고자 합니다.

대부분의 경우 정확하고 현명하게 Tailwind를 사용하면 골치 아픈 일을 방지하고 문제를 해결할 수 있습니다. 하지만 이를 위해서는 프로젝트가 충족해야 하는 두 가지 요구 사항이 있으며, 충족하지 못하면 Tailwind로 인해 오히려 작업이 매우 어려워질 수 있습니다.

첫 번째로 프로젝트에 디자인 시스템이 있어야 합니다. Tailwind의 철학은 디자이너와 개발자가 일관된 디자인 토큰을 사용하는 디자인 시스템과 함께합니다. 디자인 토큰은 디자인의 속성을 정의하고 프로젝트 전체에서 재사용되는 색상, 간격 또는 글씨 크기와 같은 원자 값입니다.

일반적인 버튼과 해당 버튼과 같은 색이어야 하는 탭이 몇 개 있다고 가정해 보겠습니다.

.button {
  background-color: oklch(45% 0.2 270);
}

.tab {
  background-color: oklch(45% 0.2 270);
}

프로젝트의 색 구성을 조금 변경하려면 의미를 명확하게 알 수 없는 이 색의 모든 인스턴스를 찾아서 모든 곳에서 업데이트해야 합니다. 이렇게 하면 일관성이 떨어지고 유지 관리가 더 어려워질 수 있습니다.

디자인 토큰은 이러한 문제를 방지하고 UI 요소 전반의 일관성을 보장하는 데 도움이 됩니다.

다행히도 디자인 토큰을 구현하려면 tailwind.config.js에 토큰을 정의하기만 하면 됩니다.

module.exports = {
  theme: {
    colors: {
      primary: 'oklch(45% 0.2 270)'
    }
  }
}

primary라는 이름의 새 색상을 추가한 후 배경색에 bg-primary를 사용하거나 애플리케이션 전체에 걸쳐 글자 색에 text-primary를 사용할 수 있습니다.

<button class="bg-primary">Standard button</button>
<div class="bg-primary">First tab</div>

이렇게 하면 프로젝트에서 색 구성을 변경할 때 tailwind.config.js 한 곳에서만 색을 변경하면 됩니다.

디자인 시스템을 고려하지 않았다면 클래스 목록에 'p-[123px] mb-[11px] gap-[3px]'와 같이 알아보기 힘들게 작성하거나 간격 설정에 15px, 16px, 17px와 같은 새 토큰을 많이 추가 해야 하는 상황이 생깁니다. 이런 경우는 결국 코드가 많이 복잡해지므로 Tailwind 사용을 피하는 것이 좋습니다.

일관된 디자인 시스템을 갖추면 개발팀과 디자인팀이 서로를 더 잘 이해할 수 있기 때문에 좋습니다.

예를 들어, Figma 내에서 디자인 시스템의 모든 값에 대해 하나의 공유 소스를 가질 수 있습니다. 하지만 이 디자인 시스템을 진짜로 유지 보수 가능하게 만들기 위해서는, 토큰 그룹화 및 이름 지정과 관련된 몇 가지 규칙을 도입해야 합니다. 이에 대해서는 이 글의 뒷부분에서 자세히 소개하겠습니다.

프로젝트가 충족해야 하는 두 번째 조건은 이미 컴포넌트 기반 접근 방식을 사용하고 있어야 한다는 것입니다. 유틸리티 우선(Utility-first) 접근 방식은 Tailwind 클래스가 요소에 직접 적용되기 때문에 상당히 복잡하고 장황한 HTML 구조로 이어질 수 있습니다. 이는 마크업을 읽고 유지 보수하기가 더 어렵다는 것을 의미하며, 특히 프로젝트가 커질수록 더욱 두드러집니다.

해결책 : 두 번 이상 사용되는 HTML 요소처럼 자주 사용되는 패턴을 별도의 컴포넌트로 캡슐화하는 컴포넌트 기반 접근 방식을 적극적으로 사용하는 것입니다.

이 접근 방식을 사용하면 모든 것을 DRY(Don't Repeat Yourself)하게 유지할 수 있습니다. 또한 Tailwind 스타일에 대한 신뢰할 수 있는 단일 소스를 확보하고 한 곳에서 쉽게 업데이트할 수 있습니다.

<!-- 재사용 가능한 버튼으로 긴 Tailwind CSS 클래스 목록을 가지고 있습니다. -->
<button class="bg-yellow-700 border-2 font-semibold border border-gray-300 text-green p-4 rounded">
Custom Button
</button>

<!-- 이 구조를 계속 반복하는 대신 재사용 가능한 컴포넌트를 만드세요. -->
<CustomButton>Custom Button</CustomButton>

개발 도구에서 코드를 컴포넌트로 분할하는 것을 허용하지 않는 경우, Tailwind의 유틸리티 우선 접근 방식은 개발을 더 어렵게 만들 뿐이므로 CSS 모듈과 같은 다른 CSS 프레임워크를 살펴보는 것이 좋습니다.

마지막으로 컴포넌트 기반 접근 방식과 관련하여 한 가지 더 강조해보면 @apply 지시문을 피하는 것이 좋습니다.

.block {
  @apply bg-red-500 text-white p-4 rounded-lg active:bg-blue-700 active:text-yellow-300 hover:bg-blue-500 hover:text-yellow-300;
}

이 지시문을 사용하면 코드가 더 깔끔해 보일 수 있지만, CSS 클래스 이름을 만들 때 고민해야 하는 일이 적고 스타일을 변경할 때 회귀가 없다는 Tailwind 주요 장점을 버리게 됩니다. 이는 @apply를 사용하면 컴포넌트 내에서 격리되지 않기 때문입니다. 또한 CSS 번들 크기가 증가합니다.

Tailwind 개발자는 문서에서도 @apply 지시문을 주의해서 사용하는 것이 중요하다고 강조했습니다.

두 가지 요구 사항을 모두 충족했다면 Tailwind CSS가 좋은 프레임워크 옵션일 가능성이 높습니다! 다음은 장기적인 사용 경험을 개선하는 데 가장 유용한 사례입니다.

1. 가능한 한 적은 수의 유틸리티 클래스 사용

HTML 요소에 대한 유틸리티 클래스 목록을 작성하면 새로운 클래스가 추가될 때마다 개발자가 코드를 분석하고 작업해야 하는 복잡성이 더해집니다(여기에는 여러분도 포함됩니다). 물론 이러한 목록은 Tailwind의 필수적이고 고유한 기능이지만, 그럼에도 불구하고 유틸리티 클래스는 가능한 한 적게 작성하는 것이 좋습니다.

다음은 클래스 수를 줄이면서도 동일한 결과를 얻을 수 있는 몇 가지 방법입니다.

  • pt-4 pb-4를 설정하는 대신 py-4를 사용하면 됩니다. 이는 px, mxmy 속성에도 적용됩니다.
  • flex flex-row justify-between 대신 flex justify-between을 사용할 수 있습니다. 이는 flex-row가 CSS에서 flex-direction 속성의 기본값이기 때문입니다. 이와 같은 사용 사례를 더 쉽게 발견할 수 있도록 flex-wrap과 같은 다른 CSS 속성의 기본값을 기억해 두면 유용할 수 있습니다.
  • border border-dotted border-2 border-black border-opacity-50과 같은 긴 클래스 목록을 작성하는 대신 border-dotted border-2 border-black/50을 설정하면 동일한 효과를 얻을 수 있습니다. border-2border가 설정되었음을 의미하고 테두리 border-black/50은 RGBA 형식의 축약어를 나타냅니다.
    클래스 목록이 짧아지면 다음에 애플리케이션의 구조를 검사할 때 무슨 일이 일어나고 있는지 분석하기가 훨씬 쉬워집니다.

2. 디자인 토큰을 그룹화하고 의미적으로 이름 짓기

팀으로 작업할 때는 변수 이름을 명확하게 지정하는 등 클린 코딩 습관이 장기적인 개발에 매우 중요하다는 데 동의하실 것입니다. 하지만 혼자 작업하는 경우에도 코드 명확성을 위한 몇 가지 규칙을 설정하는 것이 좋습니다. 그렇지 않으면 휴식 후 돌아와서 프로젝트를 다시 시작할 때 혼란스러울 수 있기 때문입니다.

이 접근 방식은 Tailwind로 작업할 때 특히 중요한데, 이렇게 많은 수의 클래스와 디자인 토큰을 무분별하게 사용하면 코드에 혼란을 가져올 수 있기 때문입니다.

위에서 설명한 것처럼 디자인 토큰을 사용하는 것은 좋은 방법이지만, 아무렇게나 붙여넣기만 하면 tailwind.config.js 파일에 혼란이 생길 수 있습니다.

이 문제를 해결하기 위해 tailwind.config.js에서 관련 토큰을 함께 그룹화 해야합니다. 그룹화 한다는 것은 중단점, 색상 등에 대한 디자인 토큰이 특정 영역에 위치하여 서로 엉키지 않게 하는 것입니다.

module.exports = {
  theme: {
    colors: {
      primary: 'oklch(75% 0.18 154)',
      secondary: 'oklch(40% 0.23 283)',
      error: 'oklch(54% 0.22 29)'
    },
    spacing: {
      'sm': '4px',
      'md': '8px',
      'lg': '12px'
    },
    screens: {
      'sm': '640px',
      'md': '768px'
    },
  },
  //...
}

토큰에 대해 하나의 의미론적 명명 규칙을 유지하면 애플리케이션이 성장함에 따라 필요한 토큰을 더 쉽게 찾고 시스템을 확장할 수 있습니다.

예를 들어 오류 상태의 색상을 추가하려면 Figma 파일에서 bright-red 토큰을 복사하여 Tailwind 설정에 붙여넣는 것이 아니라 색상 섹션에 넣고 error와 같은 더 간결한 이름을 지정합니다. 이렇게 하면 시스템의 일관성이 훨씬 더 높아집니다.

3. 클래스 순서 유지

일관된 순서를 사용하면 클래스를 더 쉽게 읽고 이해할 수 있다는 또 다른 깔끔한 코딩 규칙이 있습니다. 예를 들어 정렬되지 않은 클래스가 있는 HTML 요소를 살펴봅시다.

<div class="p-2 w-1/2 flex bg-black h-2 font-bold">
  First block with unsorted classes
</div>

<div class="italic font-mono bg-white p-4 h-2 w-3 flex">
  Second block with unsorted classes
</div>

위의 블록에는 상자 모델, 디스플레이, 타이포그래피 등 다양한 카테고리에 대한 클래스가 있지만 표현 순서가 정해져 있지 않습니다. 통합된 순서를 적용하여 카테고리별로 클래스를 정렬할 수 있습니다.

<div class="flex h-2 w-1/2 bg-black p-2 font-bold">
  First block with sorted classes
</div>

<div class="flex h-2 w-3 bg-white p-4 font-mono italic">
  Second block with sorted classes
</div>

클래스 순서를 수동으로 유지하려면 많은 시간과 주의가 필요하므로 Tailwind CSS용 공식 Prettier 플러그인을 사용하여 이 작업을 자동화하는 것이 훨씬 더 좋습니다. 이 플러그인을 사용하는 방법과 클래스 정렬 방법에 대해 자세히 알아보려면 이 글을 읽어보시기 바랍니다.

4. 빌드 크기 최소화

번들 크기를 최대한 작게 유지하는 것이 중요합니다. 빌드가 무거우면 페이지 로딩 속도가 느려지고 성능이 저하되며 사용자가 불편을 겪을 수 있기 때문입니다.

Tailwind는 수천 개의 유틸리티 클래스를 제공하며, 단일 프로젝트 내에서 모든 클래스를 사용할 가능성은 거의 없습니다. 그렇다면 사용하지 않는 스타일이 프로덕션 빌드에 포함되지 않도록 하려면 어떻게 해야 할까요?

Tailwind 버전 3.0 이상을 사용하는 경우 프로젝트에서 JIT(Just-in-Time) 엔진기본적으로 활성화되어 있으므로 필요한 만큼 CSS 스타일이 생성되므로 프로덕션 빌드에서 사용하지 않는 스타일을 제거할 필요가 없습니다.

하지만 이전 버전의 Tailwind를 사용하는 경우 빌드에 대한 추가 최적화를 수행해야 하는데, 사용하지 않는 CSS를 제거하는 도구인 PurgeCSS를 사용하여 이 작업을 수행할 수 있습니다. 이 문서에서는 버전 2.1 이상에서 이 작업을 수행하는 방법을 설명합니다. 다음과 같이 tailwind.config.js 파일에서 수동으로 JIT 모드를 활성화할 수도 있습니다.

module.exports = {
	mode: 'jit',
	...
}

이렇게 하면 번들에 필요한 스타일만 포함할 수 있습니다.

고려해야 할 또 다른 중요한 사항이 있습니다. 프로덕션 빌드의 최종 CSS를 항상 축소해야 합니다. 축소하면 공백, 주석 등 불필요한 문자가 모두 제거되므로 파일 크기가 눈에 띄게 줄어듭니다.

Tailwind CLI를 사용하면 --minify 플래그를 설정하여 이 작업을 수행할 수 있습니다.

npx tailwindcss -o build.css --minify

또는 Tailwind를 PostCSS 플러그인으로 설치한 경우 플러그인 목록에 추가하여 cssnano 도구를 사용하여 축소할 수 있습니다.

최적화를 고려하지 않으면 CSS의 크기가 수십 킬로바이트 이상으로 매우 커질 수 있습니다. 스타일이 있는 컴포넌트의 개수가 적은 작은 프로젝트에서도 CSS를 축소하고 JIT 모드를 활성화하면 30% 이상의 크기 차이가 있습니다. 이러한 효과를 보기 위해서는 위에서 설명한 대로 minify 플래그를 추가하고 jit 모드를 활성화하기만 하면 됩니다.

Tailwind의 축소 및 압축에 대한 자세한 내용은 이 문서 섹션을 참조하세요.

팁: 프로젝트에 디자인 토큰이 있는 경우 모두 실제로 사용되고 있는지 확인하세요. 사용하지 않는 디자인 토큰은 개발자를 혼란스럽게 하고 구성을 더 복잡하게 만들며 디자인 시스템에 불필요한 지저분함을 유발합니다.

5. 스타일 덮어쓰기 및 확장 시 불일치 방지

페이지에 사용자 정의 버튼이 있는 컴포넌트를 사용한다고 가정해 보겠습니다.

<Button className="bg-black" />

그리고 몇 가지 기본 스타일이 있는 Button 컴포넌트가 있습니다.

export const Button = () => {
  return <button className="bg-white">Test button</button>
}

이 경우 버튼은 흰색으로 유지됩니다. Tailwind는 스타일을 자동으로 덮어쓰지 않고 검은색을 적용하므로 Button 컴포넌트에서 이를 지정해야 합니다.

export const Button = ({ className = "bg-white" }) => {
  return <button className={className}>Test button</button>
}

Tailwind의 이러한 측면이 본질적으로 잘못된 것은 아니지만, 많은 스타일을 덮어쓰거나 확장하여 일부 모양을 커스터마이징하려는 경우 매번 프롭을 통해 클래스를 전달해야 하는 번거로움이 있을 수 있습니다.

게다가 이 접근 방식에는 단점이 하나 더 있는데, 프롭을 통해 유틸리티를 받아들이면 일관된 컴포넌트 뷰를 보장하기가 더 어려워질 수 있다는 점입니다. 이 접근 방식은 앱 전체에서 동일한 컴포넌트에 대해 아무 유틸리티나 조합하여 사용하도록 권장하므로 시각적 일관성이 부족할 수 있습니다.

그렇다면 어떻게 하면 될까요?

프롭을 통해 임의의 유틸리티 클래스를 전달할 수 있도록 허용하는 대신 미리 정의된 변형 집합을 정의합니다.

const BUTTON_VARIANTS = {
  primary: "bg-blue-500 hover:bg-blue-600 text-white",
  secondary: "bg-gray-500 hover:bg-gray-600 text-white",
  danger: "bg-red-500 hover:bg-red-600 text-white"
};

그런 다음 Button 컴포넌트를 변경하여 variant 프롭을 사용할 수 있도록 합니다. className을 더 편리하게 구성하려면 clsx를 사용할 수 있습니다.

export const Button = ({ className, variant = BUTTON_VARIANTS.primary }) => {
  return <button className={clsx(className, variant)}>Test Button</button>
}

팁: 조건부로 클래스를 구성해야 하는 경우 clsx를 사용하면 매우 ㅡ, 편리합니다.

컴포넌트에 대한 className을 생성한 후 원하는 변형(variant)을 전달하여 사용하면 됩니다.

<Button variant="secondary" />

이제 일관성이 보장되며 완전히 사용자 지정 변경에 대한 제한을 추가했음에도 불구하고 컴포넌트에 대한 새로운 변형을 추가하거나 기존 변형을 편집할 수 있는 유연성은 그대로 유지됩니다.

이 접근 방식의 또 다른 이점은 유지 보수가 더 간편해진다는 점입니다. 유틸리티 클래스를 한 곳에서 변경한 다음 앱의 해당 변형의 모든 컴포넌트에 전파할 수 있습니다.

어떠한 이유로 미리 정의된 변형 집합을 사용하고 싶지 않은 경우, 스타일 충돌 없이 JS에서 Tailwind 클래스를 병합하는 유틸리티 함수 twMerge를 제공하는 tailwind-merge 패키지를 사용해 볼 수 있지만, 가장 가볍지 않고 번들 크기가 커지므로 필요한 경우에만 신중하게 사용해야 합니다.

요약: Tailwind 사용 방법 및 사용 하면 안되는 방법

Tailwind는 강력한 도구이지만, 프로젝트에 혼란이 생기지 않도록 몇 가지 규칙을 지키면서 사용하는 것이 중요합니다. 위에 나열한 원칙을 요약해 보겠습니다.

우선, Tailwind를 최대한 활용하려면 이미 디자인 시스템과 일관된 디자인 토큰이 있고 컴포넌트 기반 접근 방식을 선택한 경우에 Tailwind를 사용해야 합니다. 재사용 가능한 요소를 컴포넌트로 나누지 않고 Tailwind를 사용하면 조만간 반복적이거나 장황한 HTML 구조로 인해 어려움을 겪게 될 것입니다.

  1. 가능한 유틸리티 클래스 수를 최소화하세요.
  2. 디자인 토큰을 그룹화하고 의미적으로 이름을 지정하는 등 팀 내에서 코드 규칙을 공식화하세요.
  3. 마찬가지로 일관된 클래스 순서를 구현하고 린터를 설정하여 깨끗한 코드를 보장하세요.
  4. 번들 크기 최소화: 필요한 스타일만 포함했는지 확인하고 프로덕션 빌드용 최종 CSS는 항상 최소화하세요.
  5. 적절한 경우 컴포넌트에 대해 미리 정의된 변형 세트를 정의하세요. 이는 컴포넌트 뷰의 불일치 및 스타일 재정의 문제를 방지하는 데 도움이 됩니다.

이 규칙을 준수하면 Tailwind를 문제없이 즐겁게 장기적으로 사용할 수 있으며, 팀원들에게 Tailwind가 제공하는 모든 혜택을 누릴 수 있는 기회를 제공할 수 있습니다.

이블 마션스에서는 성장 단계의 스타트업을 유니콘 기업으로 성장시키고, 개발자 도구를 구축하며, 오픈 소스 제품을 개발합니다. 워프 드라이브에 참여할 준비가 되셨다면 저희에게 연락주세요!

profile
FE Engineer

1개의 댓글

comment-user-thumbnail
2024년 2월 7일

좋은글 잘보고갑니다!

답글 달기