🧩 Design System7. Panda × Tailwind 호환Shared Utility Classes — Panda atomic 모드로 Tailwind-like 유틸리티 공유

Shared Utility Classes — Panda atomic 모드로 Tailwind-like 유틸리티 공유

이 문서가 답하는 질문: Panda CSS와 Tailwind 양쪽에서 동일한 클래스명(.text-primary, .bg-surface)으로 유틸리티를 쓸 수 있을까? 한 줄 답 (Pyramid Top): 가능하다. Panda atomic 모드 + 공통 토큰이면 양쪽이 같은 className으로 같은 CSS를 emit하는 공유 유틸리티 세트를 만들 수 있다. 단, 클래스 이름 충돌을 피하기 위해 prefix 또는 layer 격리가 필수다.


Why — 왜 존재하는가

<Button className="px-4 py-2 bg-primary text-white">처럼 마크업에 의미가 노출되는 유틸리티는 Tailwind의 정체성이다. Panda 사용자가 그리워하는 가장 큰 한 가지다.

케이스유틸리티가 필요한 이유
일회성 레이아웃<div className="flex gap-4 items-center"> 하나 더 쓰자고 recipe를 만들 수 없음
디자인 시스템 외부 코드마케팅 페이지·CMS HTML이 utility class만 받음
Tailwind 마이그레이션 중기존 컴포넌트가 모두 utility로 작성됨
외부 컴포넌트 스타일링Radix·Headless UI를 className으로 스타일

Panda는 css({...}) 함수 호출이 호출당 unique class(.css-1a2b3c)를 만든다. atomic 모드를 켜면 속성별 유틸리티 클래스(.flex, .gap_4)를 생성하므로, Tailwind와 직접 비교 가능한 형태가 된다.


How — 어떻게 동작하는가

핵심: 유틸리티 클래스의 이름은 다르더라도, 결과 CSS 값은 같은 변수를 가리킨다. 디자인 통일성은 이름이 아니라 이 보장한다.


What — 구체 사양·수치·예시

Panda atomic 모드 활성화

// panda.config.ts
import { defineConfig } from '@pandacss/dev'
 
export default defineConfig({
  preflight: false, // Tailwind preflight과 충돌 방지 (7장에서 다룸)
  hash: false, // 클래스 이름을 안정적으로
  prefix: 'pd', // Tailwind와 이름 충돌 방지
  jsxFramework: 'react',
  outdir: 'styled-system',
})

이러면 css({ bg: 'primary' }) 호출이 .pd-bg_primary 같은 클래스를 emit한다.

같은 토큰을 양쪽에 주입

/* tokens.css (Style Dictionary 생성) */
:root {
  --colors-primary: oklch(0.55 0.18 250);
  --spacing-4: 1rem;
}
// tailwind.config.ts
export default {
  theme: {
    extend: {
      colors: { primary: 'var(--colors-primary)' },
      spacing: { 4: 'var(--spacing-4)' },
    },
  },
}
// panda.config.ts (alias로 같은 변수 가리킴)
tokens: {
  colors: { primary: { value: 'var(--colors-primary)' } },
  spacing: { 4: { value: 'var(--spacing-4)' } },
}

결과:

  • Tailwind가 만든 .bg-primary { background: var(--colors-primary) }
  • Panda atomic이 만든 .pd-bg_primary { background: var(--colors-primary) }

이름은 다르지만 같은 변수를 가리키므로 디자인 통일.

공유 유틸리티 set을 만드는 두 가지 길

길 1: Panda atomic만 사용 (Tailwind 제거 가능)

레거시 마크업을 replace로 변환:

# className="bg-blue-500" → className="pd-bg_blue_500"
rg -l 'bg-blue-500' src/ | xargs sd 'bg-blue-500' 'pd-bg_blue_500'

장점: 빌드 시스템 단일화. 단점: Tailwind ecosystem(헤드리스 UI 라이브러리 다수)이 받는 className이 안 맞음.

길 2: 양쪽 유지 + 이름 통일 (prefix 통일)

// tailwind.config.ts
prefix: 'tw-', // .tw-bg-primary
// panda.config.ts
prefix: 'tw-', // .tw-bg_primary (Panda는 dash 대신 underscore)

이름이 완전히 같진 않지만 prefix를 통해 소속 시스템을 명시. 검색·codemod 용이.

두 유틸리티 동시 사용 (마이그레이션 중)

// Panda atomic + Tailwind 혼용. CSS variable로 디자인은 통일됨.
<div className="flex gap-4">
  <Button className="px-4 py-2 bg-primary">Tailwind</Button>
  <button className={css({ px: 4, py: 2, bg: 'primary' })}>Panda</button>
</div>

Tailwind utility로 Panda recipe 보강

가장 실용적인 패턴: Panda recipe로 컴포넌트, Tailwind utility로 layout.

import { button } from './button.recipe'
 
<div className="flex items-center gap-4 px-6">
  <button className={button({ variant: 'primary', size: 'md' })}>Save</button>
  <button className={button({ variant: 'ghost', size: 'md' })}>Cancel</button>
</div>

<button>의 내부 스타일은 recipe(button.recipe.ts)에서 강제, 부모 컨테이너의 일회성 레이아웃은 Tailwind utility. 디자인 시스템 contract는 recipe가 지키고, 페이지 변형은 utility가 흡수.


What-if — 잘못 쓰면 어떻게 깨지는가

  • 클래스 이름 충돌: Tailwind .flex와 Panda atomic .flex가 같은 셀렉터를 emit. 둘 중 마지막 emit이 이김. → prefix로 격리.
  • CSS layer 누락: Panda는 @layer utilities에, Tailwind는 @layer utilities에 같은 셀렉터를 넣으면 PostCSS 순서에 따라 결정됨 → 7장(build pipeline)에서 다룸.
  • Panda atomic의 클래스 이름은 unstable: hash 옵션이 켜져 있으면 .css-1a2b3c처럼 빌드마다 바뀜. 공유 유틸리티로 쓰려면 hash: false 필수.
  • CSS bundle 폭증: 양쪽 다 atomic 모드면 같은 utility가 두 번 생성됨. → 한쪽만 atomic, 다른 쪽은 recipe-only로.
  • Tailwind JIT가 Panda 클래스를 못 봄: className={css({...})} 안에 토큰을 쓰면 Tailwind의 content 스캔에 안 잡힘. 역도 마찬가지: Panda는 <Component className="bg-primary">를 모름. → 두 시스템이 서로의 출력에 의존하지 않게 책임 분리.

Insight — 흥미로운 이야기

Panda CSS의 atomic 모드는 사실 2022년 초기 Panda RFC에서 Tailwind와의 경쟁이 아닌 호환을 위한 escape hatch로 제안됐다. Segun Adebayo(Chakra UI·Panda 작자)는 “사람들이 Tailwind를 좋아하는 이유는 유틸리티가 아니라 디자인 시스템 토큰에 접근하는 짧은 방법이기 때문”이라고 인터뷰에서 말했다.

이 통찰이 흥미로운 이유는, 유틸리티 first vs 컴포넌트 first의 논쟁이 사실 토큰 접근 ergonomics의 문제로 환원되기 때문이다. Tailwind의 bg-primary도, Panda의 css({ bg: 'primary' })도, 결국 디자이너가 정한 한 단어(primary)를 빠르게 쓰는 길이다. 차이는 *마크업에 보이게 할지(Tailwind), TS 타입에 보이게 할지(Panda)*뿐.

@layer directive(CSS Cascade Layers, 2022~)가 표준화된 후로는 두 시스템이 명시적 우선순위로 공존할 수 있게 됐다. layer가 없던 시절엔 import 순서가 곧 cascade 순서였고, 그래서 마이그레이션이 훨씬 위험했다.


요약

  • Panda atomic 모드 + 공통 토큰이면 Tailwind-like 공유 유틸리티 세트를 만들 수 있다.
  • 이름이 완전히 같진 않더라도 같은 CSS 변수를 가리키면 디자인은 통일된다.
  • 실용 패턴: Panda recipe로 컴포넌트, Tailwind utility로 레이아웃.
  • 충돌 방지의 핵심은 prefix + @layer. (다음 문서 7에서 layer 순서.)