핵심 개념
제한된 기본 유틸리티 세트로 복잡한 컴포넌트를 구축하는 방법.
Tailwind를 사용하면 마크업 안에 단일 목적의 프레젠테이션 클래스(유틸리티 클래스)를 조합하여 스타일을 적용합니다.
You have a new message!
<div class="mx-auto flex max-w-sm items-center gap-x-4 rounded-xl bg-white p-6 shadow-lg outline outline-black/5 dark:bg-slate-800 dark:shadow-none dark:-outline-offset-1 dark:outline-white/10"> <img class="size-12 shrink-0" src="/img/logo.svg" alt="ChitChat Logo" /> <div> <div class="text-xl font-medium text-black dark:text-white">ChitChat</div> <p class="text-gray-500 dark:text-gray-400">You have a new message!</p> </div></div>
위의 UI 예제에서 사용한 유틸리티 클래스는 다음과 같습니다:
flex
, shrink-0
, p-6
)를 사용해 전체 레이아웃을 제어max-w-sm
, mx-auto
)로 카드 너비를 제한하고 가로 중앙 정렬bg-white
, rounded-xl
, shadow-lg
)로 카드 외관 스타일링size-12
)로 로고 이미지의 너비와 높이 설정gap-x-4
)로 로고와 텍스트 사이 간격 조정text-xl
, text-black
, font-medium
등)로 카드 텍스트 스타일링이런 방식으로 스타일링하는 것은 전통적인 모범 사례와 상충되지만, 한번 시도해보면 몇 가지 중요한 장점을 빠르게 느낄 수 있습니다:
이러한 장점은 작은 프로젝트에서도 큰 차이를 만들지만, 장기적으로 운영되는 대규모 프로젝트를 진행하는 팀에게는 더욱 가치가 큽니다.
이 접근 방식에 대한 일반적인 반응은 "이건 그냥 인라인 스타일 아닌가?"라는 의문입니다. 어떤 면에서는 맞습니다. 클래스 이름을 지정하고 그 클래스를 스타일링하는 대신 스타일을 직접 엘리먼트에 적용하고 있기 때문입니다.
하지만 유틸리티 클래스를 사용하면 인라인 스타일보다 중요한 장점이 많습니다. 예를 들어:
이 컴포넌트는 완전히 반응형이며 호버 및 활성 상태 스타일이 적용된 버튼을 포함하고 있으며, 전적으로 유틸리티 클래스로 구성되어 있습니다:
Erin Lindford
Product Engineer
<div class="flex flex-col gap-2 p-8 sm:flex-row sm:items-center sm:gap-6 sm:py-4 ..."> <img class="mx-auto block h-24 rounded-full sm:mx-0 sm:shrink-0" src="/img/erin-lindford.jpg" alt="" /> <div class="space-y-2 text-center sm:text-left"> <div class="space-y-0.5"> <p class="text-lg font-semibold text-black">Erin Lindford</p> <p class="font-medium text-gray-500">Product Engineer</p> </div> <button class="border-purple-200 text-purple-600 hover:border-transparent hover:bg-purple-600 hover:text-white active:bg-purple-700 ..."> Message </button> </div></div>
호버나 포커스 같은 상태에서 엘리먼트를 스타일링하려면, 대상 상태를 접두사로 붙인 유틸리티를 사용합니다. 예를 들어 hover:bg-sky-700
과 같이 작성할 수 있습니다.
이 버튼 위에 마우스를 올려 배경색이 바뀌는지 확인하세요
<button class="bg-sky-500 hover:bg-sky-700 ...">Save changes</button>
이러한 접두사는 Tailwind에서 변형(variants)이라고 부르며, 해당 변형의 조건이 일치할 때만 유틸리티 클래스의 스타일을 적용합니다.
hover:bg-sky-700
클래스에 대해 생성된 CSS는 다음과 같습니다:
.hover\:bg-sky-700 { &:hover { background-color: var(--color-sky-700); }}
이 클래스는 엘리먼트가 호버 상태일 때만 작동하며, 그 외에는 아무런 역할을 하지 않습니다. 이 클래스의 유일한 목적은 호버 스타일을 제공하는 것입니다.
이는 전통적인 CSS 작성 방식과 다릅니다. 전통적인 CSS에서는 일반적으로 하나의 클래스가 여러 상태에 대한 스타일을 제공합니다:
<button class="btn">Save changes</button><style> .btn { background-color: var(--color-sky-500); &:hover { background-color: var(--color-sky-700); } }</style>
Tailwind에서는 여러 조건이 일치할 때 유틸리티를 적용하기 위해 변형을 중첩할 수도 있습니다. 예를 들어 hover:
와 disabled:
를 결합할 수 있습니다:
<button class="bg-sky-500 disabled:hover:bg-sky-500 ...">Save changes</button>
호버, 포커스 및 기타 상태에 대한 스타일링에 대해 더 알아보려면 문서를 참고하세요.
호버(hover)와 포커스(focus) 상태와 마찬가지로, 특정 브레이크포인트에서 스타일을 적용하고 싶다면 해당 스타일 유틸리티 앞에 브레이크포인트 접두사를 붙이면 됩니다.
이 예제의 크기를 조절해 레이아웃 변화를 확인하세요
<div class="grid grid-cols-2 sm:grid-cols-3"> <!-- ... --></div>
위 예제에서 sm:
접두사는 grid-cols-3
이 sm
브레이크포인트(기본값 40rem) 이상에서만 적용되도록 합니다.
.sm\:grid-cols-3 { @media (width >= 40rem) { grid-template-columns: repeat(3, minmax(0, 1fr)); }}
더 자세한 내용은 반응형 디자인 문서에서 확인할 수 있습니다.
다크 모드에서 엘리먼트를 스타일링하려면, 다크 모드가 활성화되었을 때 적용할 유틸리티 앞에 dark:
접두사를 붙이면 됩니다.
라이트 모드
거꾸로 쓰기
제로 그래비티 펜은 어떤 방향으로도 쓸 수 있으며, 심지어 거꾸로도 쓸 수 있습니다. 우주에서도 작동합니다.
다크 모드
거꾸로 쓰기
제로 그래비티 펜은 어떤 방향으로도 쓸 수 있으며, 심지어 거꾸로도 쓸 수 있습니다. 우주에서도 작동합니다.
<div class="bg-white dark:bg-gray-800 rounded-lg px-6 py-8 ring shadow-xl ring-gray-900/5"> <div> <span class="inline-flex items-center justify-center rounded-md bg-indigo-500 p-2 shadow-lg"> <svg class="h-6 w-6 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true" > <!-- ... --> </svg> </span> </div> <h3 class="text-gray-900 dark:text-white mt-5 text-base font-medium tracking-tight ">거꾸로 쓰기</h3> <p class="text-gray-500 dark:text-gray-400 mt-2 text-sm "> 제로 그래비티 펜은 어떤 방향으로도 쓸 수 있으며, 심지어 거꾸로도 쓸 수 있습니다. 우주에서도 작동합니다. </p></div>
호버 상태나 미디어 쿼리와 마찬가지로, 중요한 점은 하나의 유틸리티 클래스가 라이트 모드와 다크 모드 스타일을 동시에 포함하지 않는다는 것입니다. 다크 모드에서 스타일을 적용하려면 여러 클래스를 사용해야 합니다. 하나는 라이트 모드 스타일을, 다른 하나는 다크 모드 스타일을 적용하는 방식입니다.
.dark\:bg-gray-800 { @media (prefers-color-scheme: dark) { background-color: var(--color-gray-800); }}
더 자세한 내용은 다크 모드 문서에서 확인할 수 있습니다.
Tailwind를 사용할 때는 종종 단일 CSS 속성 값을 구성하기 위해 여러 클래스를 함께 사용합니다. 예를 들어, 엘리먼트에 여러 필터를 추가하는 경우가 있습니다:
<div class="blur-sm grayscale"> <!-- ... --></div>
이 두 효과는 모두 CSS의 filter
속성에 의존합니다. 따라서 Tailwind는 이러한 효과를 함께 합성할 수 있도록 CSS 변수를 사용합니다:
.blur-sm { --tw-blur: blur(var(--blur-sm)); filter: var(--tw-blur,) var(--tw-brightness,) var(--tw-grayscale,);}.grayscale { --tw-grayscale: grayscale(100%); filter: var(--tw-blur,) var(--tw-brightness,) var(--tw-grayscale,);}
위에서 생성된 CSS는 약간 단순화되었지만, 여기서 핵심은 각 유틸리티가 적용하려는 효과에 대한 CSS 변수를 설정한다는 점입니다. 그런 다음 filter
속성은 이러한 모든 변수를 참조하며, 변수가 설정되지 않은 경우 아무것도 적용하지 않습니다.
Tailwind는 이와 동일한 방식을 그라디언트, 그림자 색상, 변형 등에도 사용합니다.
Tailwind의 많은 유틸리티는 테마 변수에 의해 동작합니다. 예를 들어 bg-blue-500
, text-xl
, shadow-md
와 같은 클래스들은 기본 색상 팔레트, 글꼴 크기, 그림자 설정에 매핑됩니다.
테마 외부에서 일회성 값을 사용해야 할 때는 특수한 대괄호 구문을 사용하여 임의 값을 지정할 수 있습니다:
<button class="bg-[#316ff6] ..."> Sign in with Facebook</button>
이 방법은 색상 팔레트 외부의 일회성 색상(위의 Facebook 파란색과 같은)을 사용할 때 유용할 뿐만 아니라, 매우 구체적인 그리드와 같은 복잡한 커스텀 값이 필요할 때도 유용합니다:
<div class="grid grid-cols-[24rem_2.5rem_minmax(0,1fr)]"> <!-- ... --></div>
또한 calc()
와 같은 CSS 기능을 사용해야 할 때도 유용합니다. 테마 값을 사용하더라도 이 방법을 적용할 수 있습니다:
<div class="max-h-[calc(100dvh-(--spacing(6))]"> <!-- ... --></div>
심지어 임의의 CSS 속성 이름을 포함한 완전히 임의의 CSS를 생성하는 구문도 있습니다. 이는 CSS 변수를 설정할 때 유용할 수 있습니다:
<div class="[--gutter-width:1rem] lg:[--gutter-width:2rem]"> <!-- ... --></div>
더 자세한 내용은 임의 값 사용하기 문서에서 확인할 수 있습니다.
Tailwind CSS는 다른 CSS 프레임워크처럼 하나의 거대한 정적 스타일시트가 아닙니다. 여러분이 CSS를 컴파일할 때 실제로 사용하는 클래스들을 기반으로 필요한 CSS를 생성합니다.
이를 위해 Tailwind는 프로젝트의 모든 파일을 스캔하면서 클래스 이름으로 보이는 모든 심볼을 찾습니다:
export default function Button({ size, children }) { let sizeClasses = { md: "px-4 py-2 rounded-md text-base", lg: "px-5 py-3 rounded-lg text-lg", }[size]; return ( <button type="button" className={`font-bold ${sizeClasses}`}> {children} </button> );}
Tailwind는 모든 잠재적인 클래스를 찾은 후, 각 클래스에 대한 CSS를 생성하고 여러분이 실제로 필요한 스타일만을 하나의 스타일시트로 컴파일합니다.
CSS는 클래스 이름을 기반으로 생성되기 때문에, Tailwind는 bg-[#316ff6]
과 같이 임의의 값을 사용하는 클래스도 인식하고, 해당 값이 테마에 포함되어 있지 않더라도 필요한 CSS를 생성할 수 있습니다.
이 작동 방식에 대해 더 자세히 알고 싶다면 소스 파일에서 클래스 감지하기를 참고하세요.
때로는 여러 조건이 조합된 상황에서 엘리먼트를 스타일링해야 할 때가 있습니다. 예를 들어, 다크 모드에서 특정 브레이크포인트에 있을 때, 호버 상태일 때, 그리고 엘리먼트가 특정 데이터 속성을 가지고 있을 때와 같은 경우입니다.
Tailwind를 사용하면 다음과 같이 표현할 수 있습니다:
<button class="dark:lg:data-current:hover:bg-indigo-600 ..."> <!-- ... --></button>
@media (prefers-color-scheme: dark) and (width >= 64rem) { button[data-current]:hover { background-color: var(--color-indigo-600); }}
Tailwind는 group-hover
와 같은 기능도 지원합니다. 이를 통해 특정 부모 엘리먼트가 호버 상태일 때 자식 엘리먼트를 스타일링할 수 있습니다:
<a href="#" class="group rounded-lg p-8"> <!-- ... --> <span class="group-hover:underline">Read more…</span></a>
@media (hover: hover) { a:hover span { text-decoration-line: underline; }}
이 group-*
구문은 group-focus
, group-active
와 같은 다른 변형(variants)에서도 동작하며, 더 많은 기능을 지원합니다.
특히 복잡한 시나리오에서 (특히 제어할 수 없는 HTML을 스타일링할 때), Tailwind는 임의의 변형(arbitrary variants)을 지원합니다. 이를 통해 클래스 이름에 직접 원하는 선택자를 작성할 수 있습니다:
<div class="[&>[data-active]+span]:text-blue-600 ..."> <span data-active><!-- ... --></span> <span>This text will be blue</span></div>
div > [data-active] + span { color: var(--color-blue-600);}
Tailwind CSS 프로젝트에서 인라인 스타일은 여전히 매우 유용합니다. 특히 데이터베이스나 API와 같은 동적 소스에서 값을 가져올 때 유용하게 사용할 수 있습니다.
export function BrandedButton({ buttonColor, textColor, children }) { return ( <button style={{ backgroundColor: buttonColor, color: textColor, }} className="rounded-md px-3 py-1.5 font-medium" > {children} </button> );}
또한, 클래스 이름으로 포맷했을 때 읽기 어려운 매우 복잡한 임의의 값을 다룰 때도 인라인 스타일을 사용할 수 있습니다.
<div class="grid-[2fr_max(0,var(--gutter-width))_calc(var(--gutter-width)+10px)]"><div style="grid-template-columns: 2fr max(0, var(--gutter-width)) calc(var(--gutter-width) + 10px)"> <!-- ... --></div>
또 다른 유용한 패턴은 동적 소스를 기반으로 CSS 변수를 설정하고, 이를 유틸리티 클래스에서 참조하는 것입니다.
export function BrandedButton({ buttonColor, buttonColorHover, textColor, children }) { return ( <button style={{ "--bg-color": buttonColor, "--bg-color-hover": buttonColorHover, "--text-color": textColor, }} className="bg-(--bg-color) text-(--text-color) hover:bg-(--bg-color-hover) ..." > {children} </button> );}
유틸리티 클래스만 사용해 전체 프로젝트를 구축할 때, 동일한 디자인을 여러 곳에서 재현하기 위해 특정 패턴을 반복하는 상황을 마주하게 됩니다.
예를 들어, 아래 예제에서는 각 아바타 이미지에 대한 유틸리티 클래스가 다섯 번 반복됩니다:
<div> <div class="flex items-center space-x-2 text-base"> <h4 class="font-semibold text-slate-900">Contributors</h4> <span class="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>
너무 걱정하지 마세요! 실제로 이는 크게 문제가 되지 않으며, 이를 해결하기 위한 전략은 이미 일상적으로 사용하고 있는 방법들입니다.
렌더링된 페이지에서 여러 번 나타나는 디자인 요소는 실제로는 한 번만 작성되는 경우가 많습니다. 실제 마크업은 반복문 안에서 렌더링되기 때문입니다.
예를 들어, 이 가이드의 시작 부분에 있는 중복된 아바타들은 실제 프로젝트에서는 거의 확실히 반복문 안에서 렌더링될 것입니다:
<div> <div class="flex items-center space-x-2 text-base"> <h4 class="font-semibold text-slate-900">Contributors</h4> <span class="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>
이렇게 반복문 안에서 엘리먼트를 렌더링할 때, 실제 클래스 목록은 한 번만 작성되므로 중복 문제를 해결할 필요가 없습니다.
단일 파일 내에서 특정 요소 그룹에 중복이 발생한 경우, 가장 간단한 해결 방법은 멀티 커서 편집을 사용해 각 요소의 클래스 목록을 한 번에 선택하고 수정하는 것입니다.
이 방법이 가장 효과적인 경우가 많다는 사실에 놀랄 수도 있습니다. 모든 중복된 클래스 목록을 동시에 빠르게 수정할 수 있다면, 추가적인 추상화를 도입할 필요가 없습니다.
<nav class="flex justify-center space-x-4"> <a href="/dashboard" class="font-medium rounded-lg px-3 py-2 text-gray-700 hover:bg-gray-100 hover:text-gray-900"> Home </a> <a href="/team" class="font-medium rounded-lg px-3 py-2 text-gray-700 hover:bg-gray-100 hover:text-gray-900"> Team </a> <a href="/projects" class="font-medium rounded-lg px-3 py-2 text-gray-700 hover:bg-gray-100 hover:text-gray-900"> Projects </a> <a href="/reports" class="font-medium rounded-lg px-3 py-2 text-gray-700 hover:bg-gray-100 hover:text-gray-900"> Reports </a></nav>
여러 파일에서 동일한 스타일을 재사용해야 한다면, React, Svelte, Vue와 같은 프론트엔드 프레임워크를 사용 중이라면 컴포넌트를, Blade, ERB, Twig, Nunjucks와 같은 템플릿 언어를 사용 중이라면 템플릿 부분을 만드는 것이 가장 좋은 전략입니다.
export function VacationCard({ img, imgAlt, eyebrow, title, pricing, url }) { return ( <div> <img className="rounded-lg" src={img} alt={imgAlt} /> <div className="mt-4"> <div className="text-xs font-bold text-sky-500">{eyebrow}</div> <div className="mt-1 font-bold text-gray-700"> <a href={url} className="hover:underline"> {title} </a> </div> <div className="mt-2 text-sm text-gray-600">{pricing}</div> </div> </div> );}
이제 이 컴포넌트를 원하는 만큼 여러 곳에서 사용할 수 있으며, 스타일을 한 곳에서 쉽게 업데이트할 수 있는 단일 진실 공급원(Single Source of Truth)을 유지할 수 있습니다.
React나 Vue 같은 프레임워크 대신 ERB나 Twig 같은 템플릿 언어를 사용한다면, 버튼처럼 간단한 요소를 위해 템플릿 파셜을 만드는 것이 btn
같은 간단한 CSS 클래스에 비해 과도하게 느껴질 수 있습니다.
더 복잡한 컴포넌트의 경우 템플릿 파셜을 만드는 것이 권장되지만, 템플릿 파셜이 과하다고 느껴질 때는 커스텀 CSS를 작성하는 것도 괜찮습니다.
다음은 테마 변수를 사용하여 디자인을 일관되게 유지하는 btn-primary
클래스의 예시입니다:
<button class="btn-primary">Save changes</button> Save changes</button>
@import "tailwindcss";@layer components { .btn-primary { border-radius: calc(infinity * 1px); background-color: var(--color-violet-500); padding-inline: --spacing(5); padding-block: --spacing(2); font-weight: var(--font-weight-semibold); color: var(--color-white); box-shadow: var(--shadow-md); &:hover { @media (hover: hover) { background-color: var(--color-violet-700); } } }}
다시 말하지만, 단일 HTML 엘리먼트보다 복잡한 경우에는 스타일과 구조를 한 곳에 캡슐화할 수 있도록 템플릿 파셜을 사용하는 것을 강력히 권장합니다.