Tailwind는 유틸리티 우선 방식의 작업 흐름을 권장합니다. 이 방식은 낮은 수준의 유틸리티 클래스만 사용하여 디자인을 구현합니다. 이는 조기 추상화와 그로 인한 문제점을 피할 수 있는 강력한 방법입니다.

하지만 프로젝트가 커지면, 여러 곳에서 동일한 디자인을 재현하기 위해 공통 유틸리티 조합을 반복적으로 사용하게 됩니다.

예를 들어, 아래 템플릿에서 각 아바타 이미지에 대한 유틸리티 클래스가 다섯 번 반복되는 것을 볼 수 있습니다.

Contributors

204
<div>
  <div class="flex items-center space-x-2 text-base">
    <h4 class="font-semibold text-slate-900">Contributors</h4>
    <span class="rounded-full bg-slate-100 px-2 py-1 text-xs font-semibold text-slate-700">204</span>
  </div>
  <div class="mt-3 flex -space-x-2 overflow-hidden">
    <img class="inline-block h-12 w-12 rounded-full ring-2 ring-white" src="https://images.unsplash.com/photo-1491528323818-fdd1faba62cc?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80" alt=""/>
    <img class="inline-block h-12 w-12 rounded-full ring-2 ring-white" src="https://images.unsplash.com/photo-1550525811-e5869dd03032?ixlib=rb-1.2.1&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80" alt=""/>
    <img class="inline-block h-12 w-12 rounded-full ring-2 ring-white" src="https://images.unsplash.com/photo-1500648767791-00dcc994a43e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2.25&w=256&h=256&q=80" alt=""/>
    <img class="inline-block h-12 w-12 rounded-full ring-2 ring-white" src="https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80" alt=""/>
    <img class="inline-block h-12 w-12 rounded-full ring-2 ring-white" src="https://images.unsplash.com/photo-1517365830460-955ce3ccd263?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80" alt=""/>
  </div>
  <div class="mt-3 text-sm font-medium">
    <a href="#" class="text-blue-500">+ 198 others</a>
  </div>
</div>

걱정하지 마세요! 이 가이드에서는 프로젝트에서 스타일을 재사용하는 다양한 전략과 각 전략을 사용해야 할 때의 모범 사례를 배우게 됩니다.

에디터와 언어 기능 활용하기

이와 같은 중복은 실제로 문제가 되지 않는 경우가 많습니다. 모든 코드가 한 곳에 모여 있거나, 배열을 순회하며 마크업을 한 번만 작성하기 때문입니다.

재사용해야 할 스타일이 단일 파일 내에서만 필요하다면, 멀티 커서 편집과 반복문을 사용하는 것이 중복을 관리하는 가장 간단한 방법입니다.

멀티 커서 편집

단일 파일 내에서 특정 요소 그룹에 중복이 발생한 경우, 가장 간단한 해결 방법은 멀티 커서 편집을 사용하여 각 요소의 클래스 목록을 한 번에 선택하고 편집하는 것입니다:

<nav class="flex justify-center space-x-4">
  <a href="/dashboard" class="font-medium px-3 py-2 text-slate-700 rounded-lg hover:bg-slate-100 hover:text-slate-900">Home</a>
  <a href="/team" class="font-medium px-3 py-2 text-slate-700 rounded-lg hover:bg-slate-100 hover:text-slate-900">Team</a>
  <a href="/projects" class="font-medium px-3 py-2 text-slate-700 rounded-lg hover:bg-slate-100 hover:text-slate-900">Projects</a>
  <a href="/reports" class="font-medium px-3 py-2 text-slate-700 rounded-lg hover:bg-slate-100 hover:text-slate-900">Reports</a>
</nav>

이 방법이 가장 효과적인 경우가 많다는 사실에 놀라실 수도 있습니다. 모든 중복된 클래스 목록을 동시에 빠르게 편집할 수 있다면, 추가적인 추상화를 도입할 필요가 없습니다.

반복문

템플릿에서 컴포넌트를 추출하거나 커스텀 클래스를 만들기 전에, 해당 요소가 실제로 여러 번 사용되는지 확인하세요.

렌더링된 페이지에서 여러 번 나타나는 디자인 요소는 실제로는 한 번만 작성되는 경우가 많습니다. 이는 실제 마크업이 반복문 안에서 렌더링되기 때문입니다.

예를 들어, 이 가이드의 시작 부분에 있는 중복된 아바타는 실제 프로젝트에서 거의 확실히 반복문 안에서 렌더링될 것입니다:

Contributors

204
<div>
  <div class="flex items-center space-x-2 text-base">
    <h4 class="font-semibold text-slate-900">Contributors</h4>
    <span class="rounded-full bg-slate-100 px-2 py-1 text-xs font-semibold text-slate-700">204</span>
  </div>
  <div class="mt-3 flex -space-x-2 overflow-hidden">
    {#each contributors as user}
      <img class="inline-block h-12 w-12 rounded-full ring-2 ring-white" src="{user.avatarUrl}" alt="{user.handle}"/>
    {/each}
  </div>
  <div class="mt-3 text-sm font-medium">
    <a href="#" class="text-blue-500">+ 198 others</a>
  </div>
</div>

원한다면 네비게이션 예제도 반복문이나 map을 사용해 다시 작성할 수 있습니다:

<nav className="flex sm:justify-center space-x-4">
  {[
    ['Home', '/dashboard'],
    ['Team', '/team'],
    ['Projects', '/projects'],
    ['Reports', '/reports'],
  ].map(([title, url]) => (
    <a href={url} className="rounded-lg px-3 py-2 text-slate-700 font-medium hover:bg-slate-100 hover:text-slate-900">{title}</a>
  ))}
</nav>

이렇게 반복문 안에서 엘리먼트를 렌더링하면 실제 클래스 목록은 한 번만 작성되므로 중복 문제를 해결할 필요가 없습니다.


컴포넌트와 부분 템플릿 추출하기

여러 파일에서 스타일을 재사용해야 한다면, React, Svelte, Vue 같은 프론트엔드 프레임워크를 사용 중이라면 컴포넌트를, Blade, ERB, Twig, Nunjucks 같은 템플릿 언어를 사용 중이라면 부분 템플릿을 만드는 것이 가장 좋은 전략입니다.

Beach
Private Villa
$299 USD per night
VacationCard.vue
<template>
  <div>
    <img class="rounded" :src="img" :alt="imgAlt">
    <div class="mt-2">
      <div>
        <div class="text-xs text-slate-600 uppercase font-bold tracking-wider">{{ eyebrow }}</div>
        <div class="font-bold text-slate-700 leading-snug">
          <a :href="url" class="hover:underline">{{ title }}</a>
        </div>
        <div class="mt-2 text-sm text-slate-600">{{ pricing }}</div>
      </div>
    </div>
  </div>
</template>

<script>
  export default {
    props: ['img', 'imgAlt', 'eyebrow', 'title', 'pricing', 'url']
  }
</script>

이제 이 컴포넌트를 원하는 만큼 여러 곳에서 사용할 수 있습니다. 스타일은 단일 소스에서 관리되기 때문에 한 곳에서 쉽게 업데이트할 수 있습니다.

CSS 추상화와 비교

컴포넌트가 단일 HTML 엘리먼트가 아닌 경우, CSS만으로는 컴포넌트를 정의하는 데 필요한 정보를 충분히 담아낼 수 없습니다. 조금이라도 복잡한 구조라면 HTML 구조는 CSS만큼 중요합니다.

복잡한 컴포넌트를 CSS 클래스로 추출하려고 하지 마세요

ChitChat

You have a new message!

<!-- 커스텀 CSS를 사용하더라도 이 HTML 구조를 반복해야 합니다 -->
<div class="chat-notification">
  <div class="chat-notification-logo-wrapper">
    <img class="chat-notification-logo" src="/img/logo.svg" alt="ChitChat Logo">
  </div>
  <div class="chat-notification-content">
    <div class="chat-notification-title">ChitChat</div>
    <p class="chat-notification-message">You have a new message!</p>
  </div>
</div>

<style>
  .chat-notification { /* ... */ }
  .chat-notification-logo-wrapper { /* ... */ }
  .chat-notification-logo { /* ... */ }
  .chat-notification-content { /* ... */ }
  .chat-notification-title { /* ... */ }
  .chat-notification-message { /* ... */ }
</style>

이렇게 컴포넌트 내의 각 엘리먼트에 클래스를 만들어도, 이 컴포넌트를 사용할 때마다 HTML을 반복해야 합니다. 물론 모든 인스턴스의 폰트 크기를 한 곳에서 업데이트할 수는 있지만, 제목을 링크로 바꿔야 한다면 어떻게 될까요?

컴포넌트와 템플릿 부분은 CSS만으로 추상화하는 것보다 이 문제를 훨씬 잘 해결합니다. 컴포넌트는 HTML과 스타일을 모두 캡슐화할 수 있기 때문입니다. 모든 인스턴스의 폰트 크기를 변경하는 것은 CSS만 사용할 때와 마찬가지로 쉽지만, 이제는 모든 제목을 한 곳에서 링크로 바꿀 수도 있습니다.

템플릿 부분 또는 JavaScript 컴포넌트를 만드세요

ChitChat

You have a new message!

Notification.jsx
function Notification({ imageUrl, imageAlt, title, message }) {
  return (
    <div className="p-6 max-w-sm mx-auto bg-white rounded-xl shadow-md flex items-center space-x-4">
      <div className="shrink-0">
        <img className="h-12 w-12" src={imageUrl.src} alt={imageAlt}>
      </div>
      <div>
        <div className="text-xl font-medium text-black">{title}</div>
        <p className="text-slate-500">{message}</p>
      </div>
    </div>
  )
}

이렇게 컴포넌트와 템플릿 부분을 만들면, 스타일에 대한 단일 출처가 이미 있기 때문에 유틸리티 클래스 외에 다른 것을 사용할 이유가 없습니다.


@apply로 클래스 추출하기

ERB나 Twig 같은 전통적인 템플릿 언어를 사용한다면, 버튼처럼 작은 요소를 위해 템플릿 부분을 만드는 것이 btn 같은 간단한 CSS 클래스에 비해 과도하게 느껴질 수 있습니다.

더 복잡한 컴포넌트에는 적절한 템플릿 부분을 만드는 것이 권장되지만, 템플릿 부분이 과하다고 느껴질 때는 Tailwind의 @apply 지시어를 사용해 반복되는 유틸리티 패턴을 커스텀 CSS 클래스로 추출할 수 있습니다.

다음은 기존 유틸리티를 조합해 btn-primary 클래스를 만드는 예제입니다:

HTML
<!-- 커스텀 클래스 추출 전 -->
<button class="py-2 px-5 bg-violet-500 text-white font-semibold rounded-full shadow-md hover:bg-violet-700 focus:outline-none focus:ring focus:ring-violet-400 focus:ring-opacity-75">
  Save changes
</button>

<!-- 커스텀 클래스 추출 후 -->
<button class="btn-primary">
  Save changes
</button>
CSS
@tailwind base;
@tailwind components;
@tailwind utilities;

@layer components {
  .btn-primary {
    @apply py-2 px-5 bg-violet-500 text-white font-semibold rounded-full shadow-md hover:bg-violet-700 focus:outline-none focus:ring focus:ring-violet-400 focus:ring-opacity-75;
  }
}

@apply@layer에 대해 더 알아보려면 함수와 지시문 문서를 참고하세요.

조급한 추상화를 피하자

무엇을 하든, 단지 “깔끔해 보이기 위해” @apply를 사용하지 마세요. Tailwind 클래스로 가득 찬 HTML 템플릿이 보기 흉할 수는 있지만, 커스텀 CSS가 잔뜩 들어간 프로젝트에서 변경을 하는 것은 더 나쁩니다.

만약 모든 것에 @apply를 사용하기 시작한다면, 여러분은 기본적으로 CSS를 다시 작성하는 것이고, Tailwind가 제공하는 워크플로우와 유지보수성의 장점을 모두 버리는 것입니다. 예를 들어:

  • 클래스 이름을 계속 생각해야 함 — 이름을 붙일 필요가 없는 것에 클래스 이름을 짓는 것만큼 여러분을 느리게 하거나 에너지를 빼앗는 일은 없습니다.
  • 변경을 위해 여러 파일을 오가야 함 — 모든 것을 한곳에 모아두는 것보다 훨씬 더 큰 워크플로우 방해 요소입니다.
  • 스타일 변경이 두려워짐 — CSS는 전역적입니다. 그 클래스의 min-width 값을 변경해도 사이트의 다른 부분이 망가지지 않을 것이라고 확신할 수 있나요?
  • CSS 번들이 더 커짐 — 이런.

@apply를 사용할 거라면, 버튼이나 폼 컨트롤과 같이 매우 작고 재사용성이 높은 요소에만 사용하세요. 그리고 React와 같은 프레임워크를 사용하지 않는 경우에만 사용하는 것이 좋습니다. 컴포넌트가 더 나은 선택일 수 있습니다.