--- id: frontend-css-migration-tailwind title: CSS Migration — CSS-in-JS → Tailwind / native category: Coding status: draft source_trust_level: B verification_status: conceptual created_at: 2026-05-09 updated_at: 2026-05-09 tags: [frontend, css, vibe-coding] tech_stack: { language: "CSS / TS", applicable_to: ["Frontend"] } applied_in: [] aliases: [CSS migration, Tailwind 4, Panda CSS, vanilla extract, CSS-in-JS death, runtime cost] --- # CSS Migration > CSS-in-JS (Emotion, styled-components) 가 cost 큰. **Tailwind 4 / native CSS / Panda CSS 가 modern**. ## 📖 핵심 개념 - CSS-in-JS = runtime cost (parse, hydrate). - Tailwind = build-time, 0 runtime. - Panda CSS / Vanilla Extract = atomic CSS-in-JS. - Native CSS = modern feature 가 충분. ## 💻 코드 패턴 ### 옛 (Emotion / styled-components) ```tsx import styled from '@emotion/styled'; const Button = styled.button<{ primary?: boolean }>` padding: 8px 16px; background: ${({ primary }) => primary ? 'blue' : 'gray'}; color: white; border-radius: 4px; `; ``` → Runtime parsing. Hydration cost. SSR slow. ### Tailwind ```tsx // 또는 cva import { cva } from 'class-variance-authority'; const button = cva('px-4 py-2 rounded', { variants: { color: { blue: 'bg-blue-500 text-white', gray: 'bg-gray-500 text-white' }, }, }); ``` → 0 runtime. Build-time only. ### Tailwind 4 (Lightning CSS) ```css @import "tailwindcss"; @theme { --color-primary: oklch(0.7 0.15 270); } ``` → JS config 없음. CSS-native. ### Panda CSS (atomic CSS-in-JS) ```tsx import { css } from '../styled-system/css'; ``` → Build-time atomic CSS. Type-safe. Tailwind 식 + JS DX. ### Vanilla Extract (zero-runtime) ```ts // button.css.ts import { style } from '@vanilla-extract/css'; export const button = style({ padding: '8px 16px', background: 'blue', color: 'white', }); ``` ```tsx import { button } from './button.css'; ``` → TypeScript + zero runtime. ### CSS Modules (legacy 가 OK) ```tsx // Button.module.css .button { padding: 8px 16px; background: blue; } // Button.tsx import styles from './Button.module.css'; ``` → Scope 자동. Modern alternative 가 native @scope. ### Migration: styled-components → Tailwind ``` Step 1: Add Tailwind. Step 2: 매 component 의 css = Tailwind class. Step 3: Remove styled-components. → 점진. 1 component 씩. ``` ### Migration script ```bash # Twin macro 등 의 도구 npx codemod styled-components-to-tailwind ``` → Manual review 가 필요. ### Why migrate? ``` CSS-in-JS cost: - Bundle size (Emotion: 12 KB). - Runtime parse. - Hydration delay. - SSR overhead. - Devtools 가 cryptic class. Tailwind: - 0 runtime. - 1 css file. - Browser cache. - Devtools 친화 (class). → Production performance ↑. ``` ### Cons of Tailwind ``` - Class string 가 길음 (long). - Learn curve. - Custom design system = config. - Dynamic style 어려움 (config 만). → shadcn/ui + cva 가 답. ``` ### Dynamic style (Tailwind) ```tsx // ❌ 동적 class
...
// → JIT 가 build 시 detect 못 함. // ✅ 명시 const colorClass = { red: 'bg-red-500', blue: 'bg-blue-500', }[color];
...
``` ### Tailwind v4 의 변화 ``` - CSS-native (no JS config). - Lightning CSS (Rust). - @theme directive. - 100x 빠른 build. - 작은 css output. ``` ### Native CSS의 modern features ```css /* Container query */ .card { container-type: inline-size; } @container (min-width: 400px) { ... } /* :has() */ .form:has(:invalid) { ... } /* @scope */ @scope (.card) { h2 { color: blue; } } /* CSS variables */ :root { --primary: oklch(0.7 0.15 270); } ``` → Tailwind 없이도 CSS 가 powerful. ### Theme (Tailwind) ```css @theme { --color-primary: oklch(0.7 0.15 270); --font-sans: 'Inter', sans-serif; --shadow-card: 0 4px 12px rgba(0,0,0,0.1); } ``` ```tsx
...
``` ### Dark mode ```css @media (prefers-color-scheme: dark) { :root { --color-bg: black; } } /* Or class-based */ .dark { --color-bg: black; } ``` ```tsx ``` ### CSS-in-JS 가 적절 한 경우 ``` - 매우 동적 (theme switch runtime). - Library 가 CSS-in-JS 사용. - Team 가 익숙. → 하지만 modern = native CSS 가 충분. ``` ### Astro CSS ```astro --- ---
...
``` → Component-scoped CSS native. ### Production - **Vercel docs**: Tailwind. - **GitHub**: 옛 Sass → 새 design system. - **Notion**: 자체 CSS. - **Stripe**: Sass + design system. - **shadcn/ui**: Tailwind. ### Real-world migration ``` 1. Audit current CSS. 2. Pick 1 (Tailwind / Panda / native). 3. Component 별 migrate. 4. Test (visual regression). 5. Remove old. → 매 milestone 의 measurable. ``` ### Bundle size ``` Emotion: 12 KB runtime. Styled-components: 16 KB. Tailwind: 0 (build). Panda: 0 (build). Vanilla Extract: 0. → Modern = 0 runtime. ``` ### LLM 도움 ``` - Style migration 의 component 별. - Tailwind class 의 codegen. - Visual regression 검증. → Cursor 의 큰 use case. ``` ### Tools ``` - Tailwind CSS (v4). - Panda CSS. - Vanilla Extract. - StyleX (Meta). - Lightning CSS. - PostCSS (legacy). ``` ### StyleX (Meta) ```ts import * as stylex from '@stylex/stylex'; const styles = stylex.create({ base: { padding: 16, background: 'blue' }, }); ``` → Atomic + zero runtime + type-safe. ## 🤔 의사결정 기준 | 상황 | 추천 | |---|---| | New project | Tailwind 4 | | Type-safe + JS | Panda CSS / Vanilla Extract | | 매우 dynamic | StyleX / Emotion | | 작은 / static | Native CSS | | Component library | shadcn (Tailwind) | | Migration | 점진 + visual test | ## ❌ 안티패턴 - **CSS-in-JS in 새 project**: runtime cost. - **Dynamic Tailwind class**: JIT miss. - **Tailwind + Sass**: 둘 다 사용. - **Migration big bang**: visual regression. - **No design tokens**: drift. ## 🤖 LLM 활용 힌트 - Tailwind 4 = modern default. - Panda / Vanilla Extract = type-safe alternative. - Native CSS 가 충분 (modern feature). - CSS-in-JS = legacy migration. ## 🔗 관련 문서 - [[Frontend_Tailwind_Architecture]] - [[Frontend_Bun_Lightning_Patterns]] - [[Web_Anchor_Positioning_CSS]]