핵심 개념
Tailwind에 커스텀 스타일을 추가하는 최적의 방법.
프레임워크를 사용할 때 가장 큰 어려움은 프레임워크가 제공하지 않는 기능이 필요할 때 어떻게 해야 할지 알아내는 것입니다.
Tailwind는 처음부터 확장성과 커스터마이징이 가능하도록 설계되었습니다. 따라서 어떤 것을 만들더라도 프레임워크와 싸우는 느낌이 들지 않습니다.
이 가이드에서는 디자인 토큰 커스터마이징, 필요할 때 제약을 벗어나는 방법, 커스텀 CSS 추가, 플러그인으로 프레임워크 확장과 같은 주제를 다룹니다.
색상 팔레트, 간격 스케일, 타이포그래피 스케일, 브레이크포인트 등을 변경하려면 tailwind.config.js
파일의 theme
섹션에 커스터마이징을 추가하세요:
/** @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>
임의 _변형_은 임의 값과 비슷하지만, 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-lg
와 text-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>
Tailwind 프로젝트에 완전히 커스텀한 CSS 규칙을 추가해야 할 때, 가장 쉬운 방법은 스타일시트에 직접 커스텀 CSS를 추가하는 것입니다:
@tailwind base;
@tailwind components;
@tailwind utilities;
.my-custom-style {
/* ... */
}
더 강력한 기능을 원한다면, @layer
지시자를 사용하여 Tailwind의 base
, components
, utilities
레이어에 스타일을 추가할 수도 있습니다:
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer components {
.my-custom-style {
/* ... */
}
}
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
레이어에 스타일을 추가할 수 있습니다.
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
h1 {
@apply text-2xl;
}
h2 {
@apply text-xl;
}
/* ... */
}
커스텀 기본 스타일을 추가할 때, 테마에 정의된 값을 참조하려면 theme
함수나 @apply
지시어를 사용하세요.
components
레이어는 프로젝트에 추가하고 싶은 복잡한 클래스들을 정의할 때 사용합니다. 이 클래스들은 유틸리티 클래스로 오버라이드할 수 있도록 설계되었습니다.
전통적으로 이러한 클래스들은 card
, btn
, badge
와 같은 형태로 사용됩니다.
@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
레이어는 또한 사용 중인 서드파티 컴포넌트에 커스텀 스타일을 추가하기에 적합한 장소입니다.
@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
레이어에 여러분만의 커스텀 유틸리티 클래스를 추가할 수 있습니다:
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer utilities {
.content-auto {
content-visibility: auto;
}
}
이 방법은 프로젝트에서 사용하고 싶은 CSS 기능이 Tailwind에 기본적으로 포함되어 있지 않을 때 유용합니다.
Tailwind에 @layer
를 사용해 추가한 커스텀 스타일은 자동으로 Tailwind의 수식어 구문을 지원합니다. 이를 통해 호버 상태, 반응형 브레이크포인트, 다크 모드 등을 처리할 수 있습니다.
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer utilities {
.content-auto {
content-visibility: auto;
}
}
<div class="lg:dark:content-auto">
<!-- ... -->
</div>
이러한 수식어가 어떻게 동작하는지 더 알아보려면 Hover, Focus, and Other States 문서를 참고하세요.
base
, components
, 또는 utilities
레이어에 추가한 커스텀 스타일은 실제로 HTML에서 사용될 때만 컴파일된 CSS에 포함됩니다.
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer components {
/* 실제로 사용하지 않으면 컴파일된 CSS에 포함되지 않음 */
.card {
/* ... */
}
}
항상 포함되어야 하는 커스텀 CSS를 추가하려면 @layer
지시어를 사용하지 않고 스타일시트에 추가하세요.
@tailwind base;
@tailwind components;
/* 이 스타일은 항상 컴파일된 CSS에 포함됨 */
.card {
/* ... */
}
@tailwind utilities;
원하는 우선순위 동작을 얻기 위해 커스텀 스타일을 적절한 위치에 배치해야 합니다. 위 예제에서는 .card
클래스를 @tailwind utilities
앞에 추가하여 유틸리티 클래스가 이를 오버라이드할 수 있도록 했습니다.
많은 양의 CSS를 작성하고 여러 파일로 나누어 관리할 때, Tailwind로 처리하기 전에 해당 파일들을 하나의 스타일시트로 합쳐야 합니다. 그렇지 않으면 @tailwind
지시문 없이 @layer
를 사용했다는 오류가 발생할 수 있습니다.
가장 쉬운 방법은 postcss-import 플러그인을 사용하는 것입니다:
module.exports = {
plugins: {
'postcss-import': {},
tailwindcss: {},
autoprefixer: {},
}
}
더 자세한 내용은 빌드 타임 임포트 문서를 참고하세요.
Vue나 Svelte 같은 컴포넌트 프레임워크는 각 컴포넌트 파일 내에 <style>
블록을 추가하여 컴포넌트별 스타일을 지원합니다.
이런 컴포넌트 스타일 내에서 @apply
나 theme
같은 기능을 사용할 수 있지만, @layer
지시자는 작동하지 않으며 @layer
가 @tailwind
지시자와 함께 사용되지 않았다는 오류가 발생합니다.
컴포넌트 스타일에서 @layer
를 사용하지 마세요
<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
없이 스타일을 추가하세요
<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 유틸리티를 사용하세요
<div class="bg-white rounded-lg p-6 shadow-xl">
<slot></slot>
</div>
CSS 파일을 사용하는 대신 Tailwind의 플러그인 시스템을 활용해 프로젝트에 커스텀 스타일을 추가할 수 있습니다:
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',
}
})
})
]
}
자신만의 플러그인을 작성하는 방법에 대해 더 알아보려면 플러그인 문서를 참고하세요.