Panda vs Tailwind Philosophy
이 문서가 답하는 질문: Tailwind와 Panda CSS는 같은 문제를 푸는가? 푼다면 어떤 부분에서 갈라지며, 그 갈림이 한 프로젝트에 공존 가능한 이유는 무엇인가. 한 줄 답 (Pyramid Top): 두 도구는 **“CSS를 빌드타임에 생성하는 컴파일러”**라는 공통 뿌리에서 출발하지만, Tailwind는 마크업 가독성에, Panda는 타입 안전한 recipe에 무게를 두며 갈라졌다. 그 갈림은 입력 API에서만 다르고 출력은 같은 CSS variables로 수렴하기에 공존이 기술적으로 가능하다.
Why — 왜 두 도구가 동시에 존재하는가
CSS-in-JS의 1세대(styled-components·Emotion)는 런타임 비용으로 무너지기 시작했다. 컴포넌트 렌더마다 스타일 객체를 직렬화하고 <style> 태그를 주입하는 비용은 React 18의 RSC 모델과 충돌했다.
두 번째 세대가 두 방향으로 나뉘었다.
| 방향 | 대표 도구 | 핵심 약속 |
|---|---|---|
| 마크업에 의미를 박는다 | Tailwind (2017) | 클래스 이름만 보고 디자인을 알 수 있다. CSS file은 더 이상 안 본다. |
| 객체 API + 빌드타임 추출 | Panda CSS (2023), vanilla-extract, Stitches (deprecated) | TypeScript로 타입 안전, 런타임 비용 0, recipe·variant 1급 |
같은 시대에 두 도구가 공존하는 이유는, 두 방향이 서로의 약점을 메우기 때문이다:
- Tailwind는 타입 안전과 복잡 variant가 약하다 → Panda의 추진력
- Panda는 마크업 가독성과 학습 곡선이 무겁다 → Tailwind의 추진력
| 풀려는 문제 | Tailwind | Panda |
|---|---|---|
| 런타임 CSS-in-JS의 비용 | JIT → 정적 CSS | AST 추출 → 정적 CSS |
| 디자인 토큰의 일관성 | theme.extend.colors | tokens.colors |
| variant 폭증 | cva(별도 lib) | cva·sva 1급 |
| 타입 안전 | weak (string class) | strong (TS codegen) |
| 마크업 의미 | utility 이름이 의미 | object 키가 의미 |
How — 두 컴파일러의 작동 원리
Tailwind v3/v4의 JIT (Just-In-Time)
contentglob이 가리키는 파일 전체를 문자열로 스캔bg-blue-500같은 토큰 패턴을 추출 (정규식 + 휴리스틱)tailwind.config의theme에서 값을 찾아 CSS rule 생성- 단일
app.css또는<style>태그에 inject
핵심 제약: 문자열 스캔이라 동적 클래스 합성이 실패한다. `bg-${color}-500`는 추출 못 함 → safelist 필요.
Tailwind v4의 CSS-first config
v4부터는 tailwind.config.ts가 선택적이 되고, CSS 안에서 @theme directive로 토큰을 선언한다.
/* app.css */
@import "tailwindcss";
@theme {
--color-primary-500: oklch(0.65 0.18 250);
--spacing-4: 1rem;
}이로써 Tailwind v4는 처음부터 CSS variables를 1급으로 다룬다 — Panda 쪽으로 한 발 다가왔다.
Panda CSS의 build-time extraction
- TypeScript AST를 분석해
css(...)·cva(...)·sva(...)호출을 정적 분석 panda.config.ts의tokens·recipes정의를 조합해 CSS rule + TS 타입 생성styled-system/폴더에 codegen —css(),cva(),recipe()헬퍼 + 토큰 타입- CSS는
@layer reset, base, tokens, recipes, utilities5층으로 분리되어 emit
핵심 차이: Panda는 AST 분석이라 동적 식별도 추적 가능하다 (제한적이지만 Tailwind보다 낫다).
What — 축별 상세 비교
1) API 비교
// Tailwind
<button className="bg-primary-500 hover:bg-primary-600 px-4 py-2 rounded-md text-white">
Submit
</button>
// Panda (atomic css)
<button className={css({
bg: 'primary.500',
_hover: { bg: 'primary.600' },
px: 4, py: 2,
rounded: 'md',
color: 'white',
})}>
Submit
</button>
// Panda (recipe)
const button = cva({
base: { px: 4, py: 2, rounded: 'md', color: 'white' },
variants: {
intent: {
primary: { bg: 'primary.500', _hover: { bg: 'primary.600' } },
danger: { bg: 'red.500', _hover: { bg: 'red.600' } },
},
},
defaultVariants: { intent: 'primary' },
})
<button className={button({ intent: 'primary' })}>Submit</button>| 축 | Tailwind | Panda atomic | Panda recipe |
|---|---|---|---|
| 가독성 | 한 줄에 모든 결정 | 객체 키 = 속성 | variant 의미가 1급 |
| 타입 체크 | IntelliSense (string) | TS 타입 (token + property) | TS 타입 (variant 조합) |
| 동적 합성 | safelist 필요 | 정적 분석 통과 시 OK | variant prop으로 자연스러움 |
| variant 폭발 | cva 별도 lib | cva 1급 | 1급 |
2) Type safety
Panda는 토큰·variant·conditional을 모두 TypeScript 타입으로 codegen한다.
// panda generated types (styled-system/types/tokens.d.ts)
export type ColorToken =
| 'primary.50' | 'primary.100' | ... | 'primary.900'
| 'red.50' | ... ;
// 사용 시
css({
bg: 'primary.500', // ✅ OK
bg: 'priamry.500', // ❌ TS error: typo
bg: 'primary.999', // ❌ TS error: not in tokens
})Tailwind는 문자열이므로 IDE 플러그인(tailwindcss-intellisense)의 자동완성에 의존한다. 타입 체크는 없다 — className="bg-priamry-500" 같은 typo가 빌드 시점에 잡히지 않는다.
| 검증 시점 | Tailwind | Panda |
|---|---|---|
| IDE 자동완성 | 플러그인 (IntelliSense) | 기본 (TS LSP) |
| 컴파일 타임 typo 감지 | ❌ (Lint 규칙 추가 가능) | ✅ |
| 런타임 typo 감지 | ❌ (className은 그냥 string) | ❌ (이미 빌드 시 막힘) |
| 미사용 토큰 경고 | 미지원 | panda lint |
3) CSS layer 구조
두 도구 모두 @layer를 사용하지만 구조가 다르다.
/* Tailwind v3 */
@layer base, components, utilities;
@layer base {
/* preflight (reset) + typography */
}
@layer components {
/* @apply 또는 .btn 같은 custom class */
}
@layer utilities {
/* .bg-blue-500, .px-4, ... */
}
/* Panda CSS */
@layer reset, base, tokens, recipes, utilities;
@layer reset { /* preset.reset */ }
@layer base { /* user global base */ }
@layer tokens { :root { --colors-primary-500: ...; } }
@layer recipes { /* .button--primary */ }
@layer utilities { /* .bg_primary_500 (atomic mode) */ }공존 시 핵심: 두 도구의 layer를 명시적으로 같은 순서로 선언하지 않으면 cascade가 뒤섞인다 → 자세히는 07번 문서.
4) 토큰 정의 비교
// tailwind.config.ts (v3)
export default {
theme: {
extend: {
colors: {
primary: {
500: '#3b82f6', // hex 직접 박기 (구식)
},
},
},
},
}
// tailwind.config.ts (v3, CSS variables 권장)
export default {
theme: {
extend: {
colors: {
primary: {
500: 'var(--colors-primary-500)', // 외부 SSOT 참조
},
},
},
},
}
// app.css (Tailwind v4)
@theme {
--color-primary-500: oklch(0.65 0.18 250);
}
// panda.config.ts
export default defineConfig({
theme: {
tokens: {
colors: {
primary: {
500: { value: '{colors.blue.500}' }, // alias 가능
// 또는
500: { value: 'oklch(0.65 0.18 250)' },
},
},
},
},
})Panda는 {colors.blue.500} 같은 alias가 1급이라 토큰 계층(primitive→semantic)을 자연스럽게 표현한다. Tailwind v3는 alias가 없어 JS 변수로 우회하거나 v4의 @theme directive로 옮겨야 한다.
5) 번들과 빌드 시간
| 지표 | Tailwind v4 | Panda |
|---|---|---|
| 콜드 빌드 (중규모 앱) | 1~3초 | 5~15초 |
| HMR | 50~200ms | 200~500ms |
| 출력 CSS 크기 (gzip) | 10~50KB | 5 |
| 번들에 들어가는 JS | 0 (compiler) | 0 (extract) + styled-system codegen (~10KB) |
Panda가 느린 이유: AST 분석 + codegen + 출력 분기가 많기 때문. 대신 타입 안전과 recipe를 얻는다.
6) Variant·composition 비교
// Tailwind + cva (or tailwind-variants)
import { cva } from 'class-variance-authority'
const button = cva('px-4 py-2 rounded-md', {
variants: {
intent: {
primary: 'bg-primary-500 text-white hover:bg-primary-600',
danger: 'bg-red-500 text-white hover:bg-red-600',
},
size: {
sm: 'text-sm', md: 'text-base', lg: 'text-lg',
},
},
compoundVariants: [
{ intent: 'primary', size: 'lg', class: 'shadow-lg' },
],
defaultVariants: { intent: 'primary', size: 'md' },
})
// Panda native cva
import { cva } from 'styled-system/css'
const button = cva({
base: { px: 4, py: 2, rounded: 'md' },
variants: {
intent: {
primary: { bg: 'primary.500', color: 'white', _hover: { bg: 'primary.600' } },
danger: { bg: 'red.500', color: 'white', _hover: { bg: 'red.600' } },
},
size: {
sm: { fontSize: 'sm' }, md: { fontSize: 'base' }, lg: { fontSize: 'lg' },
},
},
compoundVariants: [
{ intent: 'primary', size: 'lg', css: { shadow: 'lg' } },
],
defaultVariants: { intent: 'primary', size: 'md' },
})문법은 거의 동일하다. 차이는:
cva(Tailwind)는 클래스 문자열을 합성 → 런타임에 string concat- Panda
cva는 CSS 생성 + 클래스 이름 codegen → 빌드타임에 결정
7) 다크모드 비교
// Tailwind
<div className="bg-white dark:bg-gray-900 text-gray-900 dark:text-gray-100" />
// Panda
<div className={css({
bg: 'gray.50',
color: 'gray.900',
_dark: { bg: 'gray.900', color: 'gray.50' },
})} />
// Panda + semantic token (권장)
// panda.config.ts:
semanticTokens: {
colors: {
bg: {
surface: { value: { base: '{colors.gray.50}', _dark: '{colors.gray.900}' } },
},
},
}
// 사용:
<div className={css({ bg: 'bg.surface' })} /> // 자동으로 다크모드 전환Panda의 semantic token + condition은 다크모드를 컴포넌트 수준에서 보이지 않게 처리한다. Tailwind도 v4의 CSS variables + @media로 비슷하게 가능하다.
What-if — 두 도구를 잘못 이해하면
- 함정 1: “Tailwind는 inline style의 다른 이름이다” → 아님. 디자인 토큰의 강제가 핵심.
bg-[#ff00ff]는 예외고, 시스템은theme.extend.colors에서만 값을 꺼내야 함. - 함정 2: “Panda는 CSS-in-JS다” → 아님. 런타임 0. 빌드 시점에 CSS가 추출되며, 런타임에는
classNamestring만 흐른다. - 함정 3: “Panda recipe = Tailwind component class (@apply)” → 표면은 비슷하지만, recipe는 타입 안전 variant와 compound variant를 1급으로 다룬다.
@apply는 정적 조합일 뿐. - 함정 4: “두 도구 다 쓰면 번들이 2배” → 잘못된 추측. 사용된 클래스만 emit되므로 중복은 적다. 진짜 비용은 빌드 시간과 팀 인지 부하.
- 함정 5: “Tailwind v4가 나왔으니 Panda는 죽었다” → 아님. v4는 CSS variables를 끌어왔지만 타입 안전 recipe는 여전히 별도 lib(
tv,cva)에 의존. Panda의 강점은 그대로다.
Insight — 두 도구가 만들어진 시점
Tailwind는 2017년, Adam Wathan이 “CSS file 안 보고 마크업만으로 디자인을 끝내고 싶다”는 동기에서 시작했다. 당시 BEM·atomic CSS 진영의 클래스 이름 짓기 지옥에 대한 반격이었다.
Panda CSS는 2023년, Chakra UI 팀(Segun Adebayo)이 Stitches(deprecated)·vanilla-extract의 계보를 잇기 위해 만들었다. Chakra UI v3가 Panda 위에 재구축되면서, 이 도구는 “Chakra의 차세대 엔진”이라는 정체성을 얻었다.
두 도구는 CSS의 같은 문제를 풀지만, 출발점이 다르다:
- Tailwind: HTML을 사랑하는 사람의 도구 — 마크업이 진실의 출처
- Panda: TypeScript를 사랑하는 사람의 도구 — 타입이 진실의 출처
이 출발점의 차이가 공존을 어렵게 만들지만 동시에 가능하게 만든다. 두 진영이 같은 CSS variables를 보면 결국 같은 화면을 그린다 — 그것이 다음 문서 02 CSS Variables as Bridge의 핵심이다.
흥미로운 반전: Tailwind v4의 @theme directive는 Panda의 tokens 개념을 CSS 안으로 옮긴 것에 가깝다. Panda의 영향이 Tailwind에 역으로 흘러간 셈이다.
요약
- 두 도구는 같은 출력(정적 CSS) + 같은 메커니즘(CSS variables) + 다른 입력 API(class string vs object)다.
- Tailwind는 마크업 가독성, Panda는 타입 안전 recipe에 무게 — 트레이드오프는 명확하다.
- 공존의 기술적 가능성은 출력이 같은 CSS variables라는 사실에서 나온다.
- 조직적 가능성은 별개 — 팀 숙련도·빌드 시간·DS 성숙도가 변수다.