프레임워크를 사용할 때 가장 큰 어려움은 프레임워크가 제공하지 않는 기능이 필요할 때 어떻게 해야 할지 알아내는 것입니다.

Tailwind는 처음부터 확장성과 커스터마이징이 가능하도록 설계되었습니다. 따라서 어떤 것을 만들더라도 프레임워크와 싸우는 느낌이 들지 않습니다.

이 가이드에서는 디자인 토큰 커스터마이징, 필요할 때 제약을 벗어나는 방법, 커스텀 CSS 추가, 플러그인으로 프레임워크 확장과 같은 주제를 다룹니다.

테마 커스터마이징

색상 팔레트, 간격 스케일, 타이포그래피 스케일, 브레이크포인트 등을 변경하려면 tailwind.config.js 파일의 theme 섹션에 커스터마이징을 추가하세요:

tailwind.config.js
/** @type {import('tailwindcss').Config} */
module.exports = {
  theme: {
    screens: {
      sm: '480px',
      md: '768px',
      lg: '976px',
      xl: '1440px',
    },
    colors: {
      'blue': '#1fb6ff',
      'pink': '#ff49db',
      'orange': '#ff7849',
      'green': '#13ce66',
      'gray-dark': '#273444',
      'gray': '#8492a6',
      'gray-light': '#d3dce6',
    },
    fontFamily: {
      sans: ['Graphik', 'sans-serif'],
      serif: ['Merriweather', 'serif'],
    },
    extend: {
      spacing: {
        '128': '32rem',
        '144': '36rem',
      },
      borderRadius: {
        '4xl': '2rem',
      }
    }
  }
}

테마 커스터마이징에 대해 더 알아보려면 테마 설정 문서를 참고하세요.


임의 값 사용하기

일반적으로 잘 설계된 디자인을 만들 때는 제한된 디자인 토큰 세트를 사용하지만, 가끔은 픽셀 단위로 완벽한 디자인을 위해 이러한 제약을 벗어나야 할 때가 있습니다.

예를 들어, 배경 이미지를 정확한 위치에 배치하기 위해 top: 117px 같은 값이 필요하다면, Tailwind의 대괄호 표기법을 사용하여 임의의 값으로 클래스를 즉시 생성할 수 있습니다:

<div class="top-[117px]">
  <!-- ... -->
</div>

이 방식은 기본적으로 인라인 스타일과 유사하지만, hover 같은 인터랙티브 수식어나 lg 같은 반응형 수식어와 함께 사용할 수 있다는 큰 장점이 있습니다:

<div class="top-[117px] lg:top-[344px]">
  <!-- ... -->
</div>

이 기능은 프레임워크의 모든 요소에 적용할 수 있습니다. 배경색, 폰트 크기, 의사 엘리먼트 콘텐츠 등 다양한 속성에 사용할 수 있습니다:

<div class="bg-[#bada55] text-[22px] before:content-['Festivus']">
  <!-- ... -->
</div>

또한 theme 함수를 사용하여 tailwind.config.js 파일에 정의된 디자인 토큰을 참조할 수도 있습니다:

<div class="grid grid-cols-[fit-content(theme(spacing.32))]">
  <!-- ... -->
</div>

CSS 변수를 임의 값으로 사용할 때는 var(...)로 감쌀 필요가 없습니다. 변수 이름만 제공하면 됩니다:

<div class="bg-[--my-color]">
  <!-- ... -->
</div>

임의의 속성 사용하기

Tailwind에서 기본적으로 제공하지 않는 CSS 속성을 사용해야 할 때, 대괄호 표기법을 사용해 완전히 임의의 CSS를 작성할 수 있습니다.

<div class="[mask-type:luminance]">
  <!-- ... -->
</div>

이 방식은 인라인 스타일과 매우 유사하지만, 수정자를 사용할 수 있다는 장점이 있습니다.

<div class="[mask-type:luminance] hover:[mask-type:alpha]">
  <!-- ... -->
</div>

이 방법은 특히 조건에 따라 변경되어야 하는 CSS 변수를 다룰 때 유용합니다.

<div class="[--scroll-offset:56px] lg:[--scroll-offset:44px]">
  <!-- ... -->
</div>

임의 변형(Arbitrary Variants)

임의 _변형_은 임의 값과 비슷하지만, HTML에서 직접 대괄호 표기법을 사용해 hover:{utility} 같은 내장 의사 클래스 변형이나 md:{utility} 같은 반응형 변형처럼 선택자를 즉석에서 수정할 수 있게 해줍니다.

<ul role="list">
  {#each items as item}
    <li class="lg:[&:nth-child(3)]:hover:underline">{item}</li>
  {/each}
</ul>

더 자세한 내용은 임의 변형 문서에서 확인할 수 있습니다.

공백 처리하기

값에 공백이 포함되어야 할 경우, 언더스코어(_)를 사용하면 Tailwind가 빌드 시점에 자동으로 공백으로 변환합니다:

<div class="grid grid-cols-[1fr_500px_2fr]">
  <!-- ... -->
</div>

언더스코어가 일반적이지만 공백이 유효하지 않은 상황(예: URL)에서는 Tailwind가 언더스코어를 공백으로 변환하지 않고 그대로 유지합니다:

<div class="bg-[url('/what_a_rush.png')]">
  <!-- ... -->
</div>

드물게 실제로 언더스코어를 사용해야 하지만 공백도 유효한 경우라서 모호한 상황에서는, 백슬래시로 언더스코어를 이스케이프 처리하면 Tailwind가 이를 공백으로 변환하지 않습니다:

<div class="before:content-['hello\_world']">
  <!-- ... -->
</div>

JSX와 같이 렌더링된 HTML에서 백슬래시가 제거되는 경우, String.raw()를 사용하여 백슬래시가 JavaScript 이스케이프 문자로 처리되지 않도록 합니다:

<div className={String.raw`before:content-['hello\_world']`}>
  <!-- ... -->
</div>

모호성 해결하기

Tailwind의 많은 유틸리티는 공통 네임스페이스를 공유하지만 서로 다른 CSS 속성에 매핑됩니다. 예를 들어 text-lgtext-black은 모두 text- 네임스페이스를 공유하지만, 하나는 font-size를 위한 것이고 다른 하나는 color를 위한 것입니다.

임의의 값을 사용할 때, Tailwind는 일반적으로 전달한 값을 기반으로 이 모호성을 자동으로 처리할 수 있습니다:

<!-- font-size 유틸리티를 생성합니다 -->
<div class="text-[22px]">...</div>

<!-- color 유틸리티를 생성합니다 -->
<div class="text-[#bada55]">...</div>

하지만 CSS 변수를 사용할 때와 같이 정말로 모호한 경우도 있습니다:

<div class="text-[var(--my-var)]">...</div>

이런 상황에서는 값 앞에 CSS 데이터 타입을 추가하여 Tailwind에게 기본 타입을 “힌트”로 제공할 수 있습니다:

<!-- font-size 유틸리티를 생성합니다 -->
<div class="text-[length:var(--my-var)]">...</div>

<!-- color 유틸리티를 생성합니다 -->
<div class="text-[color:var(--my-var)]">...</div>

CSS와 @layer 사용하기

Tailwind 프로젝트에 완전히 커스텀한 CSS 규칙을 추가해야 할 때, 가장 쉬운 방법은 스타일시트에 직접 커스텀 CSS를 추가하는 것입니다:

main.css
@tailwind base;
@tailwind components;
@tailwind utilities;

.my-custom-style {
  /* ... */
}

더 강력한 기능을 원한다면, @layer 지시자를 사용하여 Tailwind의 base, components, utilities 레이어에 스타일을 추가할 수도 있습니다:

main.css
@tailwind base;
@tailwind components;
@tailwind utilities;

@layer components {
  .my-custom-style {
    /* ... */
  }
}
Tailwind가 스타일을 “레이어”로 그룹화하는 이유

CSS에서 스타일시트의 규칙 순서는 두 선택자의 명시도가 동일할 때 어떤 선언이 우선하는지를 결정합니다:

.btn {
  background: blue;
  /* ... */
}

.bg-black {
  background: black;
}

여기서 두 버튼 모두 검정색이 됩니다. 왜냐하면 CSS에서 .bg-black.btn보다 뒤에 오기 때문입니다:

<button class="btn bg-black">...</button>
<button class="bg-black btn">...</button>

이를 관리하기 위해, Tailwind는 생성된 스타일을 세 가지 다른 “레이어”로 구성합니다. 이 개념은 ITCSS에서 유래했습니다.

  • base 레이어는 리셋 규칙이나 일반 HTML 엘리먼트에 적용되는 기본 스타일을 위한 것입니다.
  • components 레이어는 유틸리티로 재정의할 수 있는 클래스 기반 스타일을 위한 것입니다.
  • utilities 레이어는 항상 다른 스타일보다 우선해야 하는 작고 단일 목적의 클래스를 위한 것입니다.

이를 명시적으로 정의하면 스타일이 서로 어떻게 상호작용하는지 이해하기 쉬워지며, @layer 지시자를 사용하면 최종 선언 순서를 제어하면서도 실제 코드를 원하는 방식으로 조직할 수 있습니다.

@layer 지시자는 스타일을 해당 @tailwind 지시자로 자동 재배치하여 선언 순서를 제어하는 데 도움을 주며, 수정자사용하지 않는 커스텀 CSS 제거와 같은 기능을 커스텀 CSS에 적용할 수 있게 해줍니다.

기본 스타일 추가하기

페이지의 기본값(예: 텍스트 색상, 배경 색상, 폰트 패밀리)을 설정하려면, html 또는 body 엘리먼트에 클래스를 추가하는 것이 가장 간단한 방법입니다.

<!doctype html>
<html lang="en" class="text-gray-900 bg-gray-100 font-serif">
  <!-- ... -->
</html>

이렇게 하면 기본 스타일 결정을 마크업에 함께 두어 다른 스타일과 함께 관리할 수 있습니다. 별도의 파일에 숨겨두지 않아도 됩니다.

특정 HTML 엘리먼트에 대한 기본 스타일을 직접 추가하려면, @layer 지시어를 사용하여 Tailwind의 base 레이어에 스타일을 추가할 수 있습니다.

main.css
@tailwind base;
@tailwind components;
@tailwind utilities;

@layer base {
  h1 {
    @apply text-2xl;
  }
  h2 {
    @apply text-xl;
  }
  /* ... */
}

커스텀 기본 스타일을 추가할 때, 테마에 정의된 값을 참조하려면 theme 함수나 @apply 지시어를 사용하세요.

컴포넌트 클래스 추가하기

components 레이어는 프로젝트에 추가하고 싶은 복잡한 클래스들을 정의할 때 사용합니다. 이 클래스들은 유틸리티 클래스로 오버라이드할 수 있도록 설계되었습니다.

전통적으로 이러한 클래스들은 card, btn, badge와 같은 형태로 사용됩니다.

main.css
@tailwind base;
@tailwind components;
@tailwind utilities;

@layer components {
  .card {
    background-color: theme('colors.white');
    border-radius: theme('borderRadius.lg');
    padding: theme('spacing.6');
    box-shadow: theme('boxShadow.xl');
  }
  /* ... */
}

components 레이어에 컴포넌트 클래스를 정의하면, 필요할 때 유틸리티 클래스를 사용해 오버라이드할 수 있습니다.

<!-- 카드처럼 보이지만 모서리가 직각인 경우 -->
<div class="card rounded-none">
  <!-- ... -->
</div>

Tailwind를 사용하면 이러한 클래스가 생각보다 자주 필요하지 않을 수 있습니다. 스타일 재사용 가이드를 참고해 권장 사항을 확인하세요.

components 레이어는 또한 사용 중인 서드파티 컴포넌트에 커스텀 스타일을 추가하기에 적합한 장소입니다.

main.css
@tailwind base;
@tailwind components;
@tailwind utilities;

@layer components {
  .select2-dropdown {
    @apply rounded-b-lg shadow-md;
  }
  .select2-search {
    @apply border border-gray-300 rounded;
  }
  .select2-results__group {
    @apply text-lg font-bold text-gray-900;
  }
  /* ... */
}

커스텀 컴포넌트 스타일을 추가할 때, 테마에 정의된 값을 참조하려면 theme 함수나 @apply 지시자를 사용하세요.

커스텀 유틸리티 추가하기

Tailwind의 utilities 레이어에 여러분만의 커스텀 유틸리티 클래스를 추가할 수 있습니다:

main.css
@tailwind base;
@tailwind components;
@tailwind utilities;

@layer utilities {
  .content-auto {
    content-visibility: auto;
  }
}

이 방법은 프로젝트에서 사용하고 싶은 CSS 기능이 Tailwind에 기본적으로 포함되어 있지 않을 때 유용합니다.

커스텀 CSS에 수식어 사용하기

Tailwind에 @layer를 사용해 추가한 커스텀 스타일은 자동으로 Tailwind의 수식어 구문을 지원합니다. 이를 통해 호버 상태, 반응형 브레이크포인트, 다크 모드 등을 처리할 수 있습니다.

CSS
@tailwind base;
@tailwind components;
@tailwind utilities;

@layer utilities {
  .content-auto {
    content-visibility: auto;
  }
}
HTML
<div class="lg:dark:content-auto">
  <!-- ... -->
</div>

이러한 수식어가 어떻게 동작하는지 더 알아보려면 Hover, Focus, and Other States 문서를 참고하세요.

사용하지 않는 커스텀 CSS 제거하기

base, components, 또는 utilities 레이어에 추가한 커스텀 스타일은 실제로 HTML에서 사용될 때만 컴파일된 CSS에 포함됩니다.

main.css
@tailwind base;
@tailwind components;
@tailwind utilities;

@layer components {
  /* 실제로 사용하지 않으면 컴파일된 CSS에 포함되지 않음 */
  .card {
    /* ... */
  }
}

항상 포함되어야 하는 커스텀 CSS를 추가하려면 @layer 지시어를 사용하지 않고 스타일시트에 추가하세요.

main.css
@tailwind base;
@tailwind components;

/* 이 스타일은 항상 컴파일된 CSS에 포함됨 */
.card {
  /* ... */
}

@tailwind utilities;

원하는 우선순위 동작을 얻기 위해 커스텀 스타일을 적절한 위치에 배치해야 합니다. 위 예제에서는 .card 클래스를 @tailwind utilities 앞에 추가하여 유틸리티 클래스가 이를 오버라이드할 수 있도록 했습니다.

여러 CSS 파일 사용하기

많은 양의 CSS를 작성하고 여러 파일로 나누어 관리할 때, Tailwind로 처리하기 전에 해당 파일들을 하나의 스타일시트로 합쳐야 합니다. 그렇지 않으면 @tailwind 지시문 없이 @layer를 사용했다는 오류가 발생할 수 있습니다.

가장 쉬운 방법은 postcss-import 플러그인을 사용하는 것입니다:

postcss.config.js
module.exports = {
  plugins: {
    'postcss-import': {},
    tailwindcss: {},
    autoprefixer: {},
  }
}

더 자세한 내용은 빌드 타임 임포트 문서를 참고하세요.

레이어와 컴포넌트별 CSS

Vue나 Svelte 같은 컴포넌트 프레임워크는 각 컴포넌트 파일 내에 <style> 블록을 추가하여 컴포넌트별 스타일을 지원합니다.

이런 컴포넌트 스타일 내에서 @applytheme 같은 기능을 사용할 수 있지만, @layer 지시자는 작동하지 않으며 @layer@tailwind 지시자와 함께 사용되지 않았다는 오류가 발생합니다.

컴포넌트 스타일에서 @layer를 사용하지 마세요

Card.svelte
<div>
  <slot></slot>
</div>

<style>
  /* 이 파일은 독립적으로 처리되기 때문에 작동하지 않음 */
  @layer components {
    div {
      background-color: theme('colors.white');
      border-radius: theme('borderRadius.lg');
      padding: theme('spacing.6');
      box-shadow: theme('boxShadow.xl');
    }
  }
</style>

이는 Vue나 Svelte 같은 프레임워크가 내부적으로 모든 <style> 블록을 독립적으로 처리하고, 각 블록에 대해 PostCSS 플러그인 체인을 별도로 실행하기 때문입니다.

즉, 10개의 컴포넌트가 각각 <style> 블록을 가지고 있다면, Tailwind는 10번 별도로 실행되며, 각 실행은 다른 실행에 대해 전혀 알지 못합니다. 따라서 Tailwind는 @layer에 정의된 스타일을 해당 @tailwind 지시자로 이동시킬 수 없습니다. Tailwind가 볼 때는 이동시킬 @tailwind 지시자가 없기 때문입니다.

이에 대한 한 가지 해결책은 컴포넌트 스타일 내에서 @layer를 사용하지 않는 것입니다.

@layer 없이 스타일을 추가하세요

Card.svelte
<div>
  <slot></slot>
</div>

<style>
  div {
    background-color: theme('colors.white');
    border-radius: theme('borderRadius.lg');
    padding: theme('spacing.6');
    box-shadow: theme('boxShadow.xl');
  }
</style>

스타일의 우선순위를 제어할 수 있는 기능을 잃게 되지만, 이는 이러한 도구들이 작동하는 방식 때문에 어쩔 수 없는 부분입니다.

우리의 권장사항은 이런 식으로 컴포넌트 스타일을 전혀 사용하지 않고, Tailwind를 의도된 방식대로 사용하는 것입니다. 즉, 단일 전역 스타일시트로 사용하고 HTML에서 직접 클래스를 사용하는 것입니다.

컴포넌트 스타일 대신 Tailwind 유틸리티를 사용하세요

Card.svelte
<div class="bg-white rounded-lg p-6 shadow-xl">
  <slot></slot>
</div>

플러그인 작성하기

CSS 파일을 사용하는 대신 Tailwind의 플러그인 시스템을 활용해 프로젝트에 커스텀 스타일을 추가할 수 있습니다:

tailwind.config.js
const plugin = require('tailwindcss/plugin')

module.exports = {
  // ...
  plugins: [
    plugin(function ({ addBase, addComponents, addUtilities, theme }) {
      addBase({
        'h1': {
          fontSize: theme('fontSize.2xl'),
        },
        'h2': {
          fontSize: theme('fontSize.xl'),
        },
      })
      addComponents({
        '.card': {
          backgroundColor: theme('colors.white'),
          borderRadius: theme('borderRadius.lg'),
          padding: theme('spacing.6'),
          boxShadow: theme('boxShadow.xl'),
        }
      })
      addUtilities({
        '.content-auto': {
          contentVisibility: 'auto',
        }
      })
    })
  ]
}

자신만의 플러그인을 작성하는 방법에 대해 더 알아보려면 플러그인 문서를 참고하세요.