유틸리티 클래스로 스타일링하기

유틸리티 클래스로 스타일링하기

Tailwind를 처음 써 보면 "클래스 이름을 일일이 짓지 않고, 마크업에다 스타일을 직접 박는다?" 하고 의아해할 수 있어요. Tailwind는 이렇게 여러 개의 단일 목적 프레젠테이션 클래스, 즉 유틸리티 클래스를 조합해 UI를 만드는 프레임워크예요. 한번 손에 익으면 클래스 이름 짓느라 고민할 일도, HTML과 CSS 파일을 오가며 셀렉터를 뒤질 일도 줄어드는 게 장점이에요. 이 글에서는 유틸리티 클래스로 스타일을 쌓는 방식이 왜 빠르고 안전한지, hover·미디어 쿼리·다크 모드 같은 상태를 어떻게 다루는지 살펴볼게요.

출처: Styling with utility classes — Tailwind CSS 공식문서

유틸리티 클래스란

레이아웃·간격·색·타이포그래피 같은 걸 목적별로 쪼갠 클래스가 바로 유틸리티예요. 아래 카드를 보면 한눈에 익을 거예요.

<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>

각 클래스가 하나의 CSS 속성에만 관여해요. flex·shrink-0·p-6은 레이아웃과 패딩을, max-w-sm·mx-auto는 카드 폭과 가로 가운데 정렬을, bg-white·rounded-xl·shadow-lg는 카드의 겉모습을 담당하죠. 이렇게 조합하면 몇 가지 장점이 생겨요.

  • 빨리 완성돼요 — 클래스 이름을 짓고 셀렉터를 고민하고 파일을 오갈 필요가 없어서 디자인이 금방 끝나요.
  • 수정이 안전해요 — 유틸리티를 한 요소에 더하거나 빼는 건 그 요소에만 영향을 주니까, 다른 페이지의 스타일을 실수로 깨뜨릴 걱정이 없어요.
  • 오래된 프로젝트도 관리가 쉬워요 — 수정할 때 그 요소를 찾아 클래스만 바꾸면 되고, 반년 전에 짠 커스텀 CSS를 기억할 필요가 없어요.
  • 코드가 이식성이 좋아요 — 구조와 스타일이 한곳에 있으니 UI 덩어리를 통째로 복사해 다른 프로젝트에 옮길 수도 있어요.
  • CSS가 불어나지 않아요 — 유틸리티는 재사용성이 높아서 기능을 추가해도 CSS가 기능 수만큼 선형적으로 늘지 않아요.

작은 프로젝트에서도 체감되지만, 오래 가는 큰 프로젝트를 팀 단위로 운영할수록 이 장점이 더 커져요.

인라인 스타일과는 뭐가 다를까요

"어차피 요소에 직접 스타일을 주는데, 인라인 스타일이랑 뭐가 달라?" 하고 물을 수 있어요. 겉보기엔 비슷하지만 결정적인 차이가 있죠. 인라인 스타일은 값 하나하나가 매직 넘버지만, 유틸리티는 테마에 정의된 디자인 시스템에서 값을 고르므로 일관된 UI를 만들기 쉬워요. 또 인라인 스타일로는 hoverfocus 같은 상태, 미디어 쿼리를 다룰 수 없지만, Tailwind의 상태 변형자와 반응형 변형자를 쓰면 유틸리티만으로 모두 처리할 수 있어요.

상태·반응형·다크 모드 다루기

유틸리티 앞에 접두사를 붙이면 특정 조건에서만 스타일이 적용돼요. 이 접두사를 Tailwind에선 **변형자(variant)**라고 불러요.

  • hover/focus 상태hover:bg-sky-700처럼 쓰면 그 요소가 hover될 때만 배경이 바뀌어요. 변형자가 조건에 맞을 때만 해당 유틸리티의 스타일을 켜는 방식이라, hover 스타일 전용 클래스가 따로 생기는 구조예요. disabled:hover:bg-sky-500처럼 변형자를 쌓는 것도 가능해요.
  • 미디어 쿼리/브레이크포인트sm: 같은 브레이크포인트 접두사를 붙이면 해당 너비 이상에서만 적용돼요. 예를 들어 grid grid-cols-2 sm:grid-cols-3은 작은 화면에선 2열, sm(40rem) 이상에선 3열로 바꿔요.
  • 다크 모드dark: 접두사를 붙이면 다크 모드가 활성일 때만 그 스타일이 켜져요. bg-white dark:bg-gray-800처럼 라이트 스타일과 다크 스타일을 클래스 두 개로 나눠서 표현해요.

생성되는 CSS를 보면 hover:bg-sky-700.hover\:bg-sky-700 { &:hover { background-color: var(--color-sky-700); } }처럼 hover될 때만 동작하는 구조예요. 하나의 클래스는 "배경을 sky-700로"라는 한 상태의 일만 하죠.

클래스 조합과 임의 값

  • 클래스 조합blur-sm grayscale처럼 여러 효과를 한 요소에 쌓을 때는 CSS 변수를 이용해요. 각 유틸리티가 자신의 CSS 변수만 세팅하고, filter 속성이 그 변수들을 조합해요. 그래서 blurgrayscale을 함께 써도 서로 덮어쓰지 않지요.
  • 임의 값(arbitrary value) — 테마에 없는 1회성 값이 필요할 땐 대괄호 문법을 써요. 예를 들어 팔레트에 없는 색은 bg-[#316ff6], 아주 구체적인 그리드는 grid grid-cols-[24rem_2.5rem_minmax(0,1fr)], calc()max-h-[calc(100dvh-(--spacing(6)))]처럼 넣어요. CSS 변수를 설정해야 할 땐 [--gutter-width:1rem]처럼 프로퍼티 자체를 지정하는 문법도 있어요.

스타일 충돌 관리하기

같은 CSS 속성을 다루는 클래스 두 개를 쓰면, 스타일시트에서 나중에 나오는 클래스가 이겨요. 그래서 한 요소에 grid flex처럼 충돌하는 클래스를 넣지 말고, 실제로 적용할 하나만 넣는 게 원칙이에요. React·Vue 같은 컴포넌트 라이브러리를 쓸 땐 컴포넌트 밖에서 클래스를 추가로 붙이기보다 스타일링용 prop을 노출하는 게 충돌을 피하는 방법이에요.

반드시 강제해야 할 땐 클래스 이름 끝에 !를 붙여 !important로 만들 수 있어요(bg-red-500!). 기존 프로젝트에 이미 우선순위가 높은 CSS가 많다면 @import "tailwindcss" important;로 전체 유틸리티를 !important로 만들 수도 있고요. 다른 라이브러리와 클래스명이 충돌한다면 @import "tailwindcss" prefix(tw);처럼 접두사 옵션으로 모든 유틸리티·CSS 변수에 접두사를 붙일 수 있어요.

더 알아보기 (Learn more)