🧩 Design System7. Panda × Tailwind 호환Panda vs Tailwind Philosophy — 두 컴파일러의 철학 비교

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의 추진력
풀려는 문제TailwindPanda
런타임 CSS-in-JS의 비용JIT → 정적 CSSAST 추출 → 정적 CSS
디자인 토큰의 일관성theme.extend.colorstokens.colors
variant 폭증cva(별도 lib)cva·sva 1급
타입 안전weak (string class)strong (TS codegen)
마크업 의미utility 이름이 의미object 키가 의미

How — 두 컴파일러의 작동 원리

Tailwind v3/v4의 JIT (Just-In-Time)

  1. content glob이 가리키는 파일 전체를 문자열로 스캔
  2. bg-blue-500 같은 토큰 패턴을 추출 (정규식 + 휴리스틱)
  3. tailwind.configtheme에서 값을 찾아 CSS rule 생성
  4. 단일 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

  1. TypeScript AST를 분석해 css(...)·cva(...)·sva(...) 호출을 정적 분석
  2. panda.config.tstokens·recipes 정의를 조합해 CSS rule + TS 타입 생성
  3. styled-system/ 폴더에 codegen — css(), cva(), recipe() 헬퍼 + 토큰 타입
  4. CSS는 @layer reset, base, tokens, recipes, utilities 5층으로 분리되어 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>
TailwindPanda atomicPanda recipe
가독성한 줄에 모든 결정객체 키 = 속성variant 의미가 1급
타입 체크IntelliSense (string)TS 타입 (token + property)TS 타입 (variant 조합)
동적 합성safelist 필요정적 분석 통과 시 OKvariant prop으로 자연스러움
variant 폭발cva 별도 libcva 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가 빌드 시점에 잡히지 않는다.

검증 시점TailwindPanda
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 v4Panda
콜드 빌드 (중규모 앱)1~3초5~15초
HMR50~200ms200~500ms
출력 CSS 크기 (gzip)10~50KB530KB (atomic mode) / 1560KB (recipe mode)
번들에 들어가는 JS0 (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 cvaCSS 생성 + 클래스 이름 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가 추출되며, 런타임에는 className string만 흐른다.
  • 함정 3: “Panda recipe = Tailwind component class (@apply)” → 표면은 비슷하지만, recipe는 타입 안전 variantcompound 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 성숙도가 변수다.