본문 바로가기
C.W.K.
Stream
Lesson 05 of 07 · published

Tailwind anti-pattern — 조용히 깨지는 것들

~11 min · anti-patterns, tailwind, scanning

Level 0React 입문자
0 XP0/54 lessons0/12 achievements
0/100 XP to next level100 XP to go0% complete
어떤 스타일링 실수는 개발 환경에서는 멀쩡하다가 조건이 바뀔 때 깨져. Tailwind의 정적 분석과 테마 계약을 거스르는 패턴을 미리 알아두자.

동적으로 만든 클래스 이름

className={'bg-' + color + '-500'}는 자연스럽게 보이지만 Tailwind의 정적 스캐너는 완성된 클래스 문자열을 찾지 못해. { red: 'bg-red-500', blue: 'bg-blue-500' }처럼 가능한 값을 map에 완전한 문자열로 적어 두면 타입 검사와 CSS 생성이 모두 안정적이야.

반복되는 임의값

bg-[#ff8fbe] 같은 임의값은 정말 한 번만 쓸 때 편해. 같은 값이 두세 파일에 반복되면 브랜드 색을 바꿀 때 일부를 놓치기 쉬우니 @theme 토큰으로 올려.

!important로 cascade 우회하기

!text-red-500 같은 ! prefix는 다른 방법으로 건드릴 수 없는 서드파티 CSS를 재정의할 때만 써. 직접 만든 컴포넌트에 필요하다면 보통 레이어나 클래스 병합 계약이 잘못된 거야.

지나치게 긴 className

유틸리티가 너무 길어지면 @layer components의 클래스, 의미가 있는 상수, 조건을 정리하는 cn() 중 하나로 묶어. 긴 문자열을 Tailwind의 숙명처럼 받아들일 필요 없어.

두 스타일 시스템 섞기

Tailwind 프로젝트에 styled-components나 emotion을 함께 넣으면 같은 책임을 두 체계가 나눠 맡고 런타임 비용도 늘어. Tailwind와 @layer를 일관되게 쓰거나 CSS-in-JS를 선택하되, 특별한 이유 없이 둘을 섞지 마.

정적 스캐너는 제약이 아니라 작은 CSS를 만드는 계약이야. 소스에 보이는 유틸리티만 생성하므로 프로덕션 CSS가 작아져. 런타임 문자열 조합으로 이 계약을 깨면 필요한 CSS가 빠지는 쪽이 더 흔해.

동적 값은 완전한 클래스 문자열로 매핑해

bg-${color}-500처럼 이름 조각을 이어 붙이면 Tailwind 스캐너가 실제 클래스를 찾지 못해 프로덕션 CSS에서 빠질 수 있어. { red: 'bg-red-500', blue: 'bg-blue-500' }처럼 가능한 문자열을 소스에 완전한 형태로 적어.

정말 동적인 값에는 제한된 safelist를 써

서버 데이터가 정해진 클래스 집합 중 하나를 보낼 수밖에 없다면 Tailwind v4의 @source inline()로 필요한 문자열만 명시할 수 있어. 타입이 있는 map으로 해결할 수 있는지 먼저 확인하고 safelist 범위를 작게 유지해.

프로덕션 CSS에서 결과를 확인해

npm run build 뒤 생성된 CSS에서 기대한 유틸리티를 찾아. 개발 중 우연히 남아 있던 스타일이 배포 빌드에서 사라지는 문제를 잡을 수 있어. 직접 만든 !important와 서로 다른 스타일 시스템을 섞는 선택도 이 시점에 다시 검토해.

Code

동적 클래스 고침·tsx
// ANTI-PATTERN — 스캐너가 못 잡음
function Badge({ color }: { color: "red" | "green" | "blue" }) {
  return <span className={`bg-${color}-500 text-white`}>!</span>;
}

// FIX 1 — 명시적 map (타입 포함)
const badgeColors: Record<"red" | "green" | "blue", string> = {
  red: "bg-red-500",
  green: "bg-green-500",
  blue: "bg-blue-500",
};
function BadgeFixed({ color }: { color: keyof typeof badgeColors }) {
  return <span className={`${badgeColors[color]} text-white`}>!</span>;
}

// FIX 2 — 임의 데이터에서 진짜로 런타임 클래스 이름 필요하면 Tailwind safelist
// (vite.config 또는 @theme 에). 최후의 수단이야. 보통은 map이 더 나아.
임의값 → 테마 토큰 승격·css
/* 변경 전: 같은 hex 값이 세 파일에 흩어져 있어. */
/* <header className="bg-[#ff8fbe]"> ... </header> */
/* <footer className="text-[#ff8fbe]"> ... </footer> */
/* <ring className="ring-[#ff8fbe]"> ... </ring> */

/* 변경 후: @theme가 색상의 단일 출처가 돼. */
@theme {
  --color-brand: #ff8fbe;
}
/* <header className="bg-brand"> ... </header> */
/* <footer className="text-brand"> ... </footer> */
/* <ring className="ring-brand"> ... </ring> */

External links

Exercise

동적으로 조립한 bg-${color}-500 클래스와 세 곳에 반복한 bg-[#ff8fbe]를 프로젝트에 일부러 넣어. 프로덕션 빌드에서 첫 클래스가 빠지는지 확인한 뒤 완성된 클래스 map과 @theme 토큰으로 각각 고쳐.
Hint
고친 뒤 다시 빌드하고 결과 CSS에 bg-red-500이 있는지 확인해. 반복 색상은 한 토큰의 값을 바꿨을 때 세 곳이 함께 바뀌어야 해.

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고

댓글 0

🔔 답글 알림 (로그인 필요)
로그인댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.