-
Web에서 Style을 입히는 방법(Claude와 Css 공부 티키타카)카테고리 없음 2025. 10. 15. 22:36
브라우저 렌더링부터 CSS 라이브러리까지
목차
- 브라우저 렌더링 기초
- SCSS - CSS 전처리기
- Tailwind CSS - 유틸리티 우선 프레임워크
- Vanilla Extract - Zero-runtime CSS-in-TypeScript
- 전체 비교 및 선택 가이드
나의 생각
- Zero Runtime에 대한 궁금증을 시작으로 스터디를 시작
- 결국 CSS 파일이 생성되는 시점에 따라서 런타임 환경이 구분되는 거 아닐까? 런타임 환경에서 css-in-js가 아니라면 성능은 같지 않을까?
- 물론 컴파일 과정이나 개발 환경에서 차이점이 분명하지만 런타임 환경에서는 다를 수 없다는 결론을 내렸다.
- Vanilla Extract가 동적 스타일 변경이나 관리를 편하게 문법적 설탕을 잘 만들었다고 생각한다.
- 하지만 Zero-runtime이라는 타이틀이 Scss나 Tailwind를 사용하는 것보다 엄청 대단한 개념이나 기술이 아니라는 점도 확실히 이해했다.
- 프론트엔드 개발자가 Webpack/vite/next 등 다양한 빌드 도구에 대해서 이해해야하는 이유도 다시 한 번 확인했다.
- 도대체 빌드는 뭐고 컴파일은 뭐고 런타임은 뭘까? 라는 의문점이 있다면 개발 단계에서 Production 환경까지의 과정 그리고 내가 정의한 스타일이 브라우저에서 어떻게 적용되는지 알아보면 해결될 것 같다.
1. 브라우저 렌더링 기초
1.1 전체 렌더링 파이프라인
브라우저가 HTML, CSS, JavaScript를 화면에 그리는 전체 과정입니다.
HTML 파일 다운로드 ↓ HTML 파싱 → DOM Tree 생성 ↓ CSS 파일 다운로드 (병렬) ↓ CSS 파싱 → CSSOM Tree 생성 ↓ DOM + CSSOM → Render Tree 생성 ↓ Layout (Reflow) - 요소의 위치와 크기 계산 ↓ Paint - 실제 픽셀로 그리기 ↓ Composite - 레이어 합성 ↓ 🎨 화면에 표시1.2 DOM과 CSSOM
DOM (Document Object Model)
HTML을 트리 구조로 변환한 것입니다.
<html> <body> <div class="container"> <h1>Hello</h1> </div> </body> </html>위 HTML은 다음과 같은 DOM 트리가 됩니다:
Document └─ html └─ body └─ div.container └─ h1 └─ "Hello"CSSOM (CSS Object Model)
CSS를 트리 구조로 변환한 것입니다.
body { font-size: 16px; } .container { padding: 20px; } h1 { color: blue; }위 CSS는 다음과 같은 CSSOM 트리가 됩니다:
body ├─ font-size: 16px (자식들이 상속) └─ .container ├─ padding: 20px └─ h1 └─ color: blue1.3 중요한 특징
CSS는 렌더링 차단 리소스
- CSS 파일이 완전히 로드되고 파싱될 때까지 화면에 아무것도 그리지 않습니다
- 이유: 스타일 없이 HTML을 먼저 보여주면 깜빡임(FOUC)이 발생하기 때문
JavaScript는 DOM과 CSSOM 모두 차단
- <script> 태그를 만나면 HTML 파싱이 멈춥니다
- JavaScript가 CSSOM에도 접근 가능하므로 CSS 로딩도 기다려야 합니다
병렬 다운로드
- 여러 CSS 파일을 동시에 다운로드할 수 있습니다
- 하지만 파싱은 순차적으로 진행됩니다
1.4 화면에 실제로 보이는 시점
Composite 단계가 완료되는 순간 화면에 픽셀이 표시됩니다.
초기 렌더링 타임라인
0ms - HTML 다운로드 시작 50ms - HTML 파싱 시작, CSS 다운로드 시작 100ms - CSS 다운로드 완료, CSSOM 생성 150ms - Render Tree 생성 200ms - Layout 계산 완료 250ms - Paint 완료 300ms - Composite 완료 → 🎨 사용자가 화면을 봄!300ms까지는 사용자는 빈 화면을 보고 있습니다.
점진적 렌더링
- CSS는 반드시 기다립니다 (렌더링 차단)
- 이미지, 폰트는 기다리지 않습니다 (먼저 레이아웃을 잡고, 나중에 도착하면 다시 그립니다)
1.5 Layout (Reflow) 단계
각 요소가 화면의 어디에, 얼마나 크게 배치되어야 하는가를 계산하는 과정입니다.
계산 과정
- Render Tree를 위에서 아래로 순회 (부모 → 자식 순서)
- 박스 모델 계산 (Content → Padding → Border → Margin)
- 레이아웃 알고리즘 적용 (Block, Flexbox, Grid 등)
Reflow가 발생하는 경우
Reflow는 비용이 큰 작업이므로 브라우저는 가능한 한 피하려고 합니다.
항상 Reflow를 일으키는 것들
DOM 조작:
element.appendChild(newElement); element.remove(); element.innerHTML = '...';크기/위치 변경:
element.style.width = '500px'; element.style.height = '500px'; element.style.margin = '20px'; element.style.padding = '10px'; element.style.display = 'none';기타:
window.resize(); // 윈도우 리사이즈 element.classList.add(); // 클래스 변경Layout 정보를 읽을 때도 강제 Reflow
// 이런 속성들을 읽으면 최신 Layout을 즉시 계산 element.offsetWidth element.offsetHeight element.getBoundingClientRect() window.getComputedStyle(element)Reflow 최적화
❌ 나쁜 예 (여러 번 Reflow):
element.style.width = '100px'; // Reflow 1 element.style.height = '100px'; // Reflow 2 element.style.margin = '10px'; // Reflow 3✅ 좋은 예 (한 번만 Reflow):
// 방법 1: cssText 사용 element.style.cssText = 'width: 100px; height: 100px; margin: 10px;'; // 방법 2: class 변경 element.className = 'new-style'; // 방법 3: DocumentFragment 사용 const fragment = document.createDocumentFragment(); fragment.appendChild(element1); fragment.appendChild(element2); document.body.appendChild(fragment); // 한 번만 Reflow비용 비교
width 변경 → Layout + Paint + Composite 🔴 비용 큼 color 변경 → Paint + Composite 🟡 중간 transform → Composite만 🟢 비용 작음 opacity → Composite만 🟢 비용 작음애니메이션 최적화:
/* ❌ 나쁜 예 */ .box { transition: left 0.3s; /* Layout 발생 */ } /* ✅ 좋은 예 */ .box { transition: transform 0.3s; /* Composite만 */ }
2. SCSS - CSS 전처리기
2.1 핵심 개념
SCSS는 CSS의 확장 문법으로, 개발자가 SCSS로 작성하면 빌드 타임에 일반 CSS로 컴파일되어 브라우저에 전달됩니다.
전체 처리 과정
개발 단계 빌드 단계 브라우저 ┌─────────┐ ┌─────────┐ ┌─────────┐ │ .scss │ 컴파일 │ .css │ 로딩 │ CSSOM │ │ 파일 │ ────→ │ 파일 │ ────→ │ 생성 │ └─────────┘ └─────────┘ └─────────┘2.2 주요 기능
변수 (Variables)
$primary-color: #3498db; $padding: 20px;- 색상, 크기 등을 변수로 관리
- 재사용과 일관성 유지
중첩 (Nesting)
.card { padding: $padding; .card-header { color: $primary-color; &:hover { color: darken($primary-color, 10%); } } }- HTML 구조처럼 CSS를 중첩해서 작성
- 선택자의 계층 구조를 명확하게 표현
믹스인 (Mixins)
@mixin flex-center { display: flex; justify-content: center; align-items: center; } .container { @include flex-center; height: 100vh; }- 재사용 가능한 스타일 블록
- 매개변수를 받아 동적으로 생성 가능
상속 (Extend)
%button-base { padding: 10px 20px; border: none; cursor: pointer; } .button-primary { @extend %button-base; background: $primary-color; }- 다른 선택자의 스타일을 상속
- 중복 코드 제거
2.3 컴파일 과정
Sass 컴파일러의 처리 순서
1단계: 변수 파싱
- 모든 변수 선언을 찾아서 저장
2단계: 중첩 해제
// 입력 .card { .header { color: blue; } } // 출력 .card .header { color: blue; }3단계: 믹스인 인라인화
// 입력 .container { @include flex-center; } // 출력 .container { display: flex; justify-content: center; align-items: center; }4단계: 함수 실행
// 입력 $color: #3498db; .header { color: darken($color, 10%); } // 출력 .header { color: #2980b9; }5단계: 상속 처리
// 입력 .button-primary, .button-secondary { @extend %button-base; } // 출력 .button-primary, .button-secondary { padding: 10px 20px; border: none; }2.4 빌드 도구 통합
Webpack
module.exports = { module: { rules: [ { test: /\.scss$/, use: [ 'style-loader', // 3. JS로 CSS를 DOM에 주입 'css-loader', // 2. CSS를 JS로 변환 'sass-loader' // 1. SCSS를 CSS로 컴파일 ] } ] } };Vite
// vite.config.js export default { css: { preprocessorOptions: { scss: { additionalData: `$injectedColor: orange;` } } } };Vite는 내장 SCSS 지원이 있어서 sass 패키지만 설치하면 자동 처리됩니다.
2.5 브라우저 전달
개발 모드
<head> <style> .card { padding: 20px; background: white; } .card .card-header { color: #3498db; } .container { display: flex; justify-content: center; } </style> </head>프로덕션 모드
<head> <link rel="stylesheet" href="/assets/styles.abc123.css"> </head>/* styles.abc123.css - 압축됨 */ .card{padding:20px;background:#fff}.card .card-header{color:#3498db}.container{display:flex;justify-content:center}2.6 장단점
✅ 장점
- 빌드 타임 처리: 런타임 성능 영향 제로
- 강력한 기능: 변수, 중첩, 믹스인, 함수
- 코드 재사용성: 믹스인과 상속으로 DRY 원칙
- 중첩으로 가독성 향상: HTML 구조와 유사하게 작성
❌ 단점
- 동적 스타일 불가능: 빌드 타임에 모든 값이 결정
- 전역 네임스페이스: CSS Modules나 BEM 필요
- 빌드 설정 필요: Webpack, Vite 등 설정 필요
3. Tailwind CSS - 유틸리티 우선 프레임워크
3.1 핵심 개념
Tailwind는 미리 정의된 유틸리티 클래스를 HTML에서 조합해서 스타일을 만드는 방식입니다.
전체 처리 과정
개발 단계 빌드 단계 브라우저 ┌─────────┐ ┌──────────┐ ┌─────────┐ │ HTML │ 스캔 │ Tailwind │ CSS 생성 │ CSSOM │ │ 클래스 │ ────→ │ CLI │ ────────→ │ 생성 │ └─────────┘ └──────────┘ └─────────┘SCSS와의 차이
SCSS: CSS를 확장해서 작성 → CSS 생성 Tailwind: HTML 클래스 스캔 → 사용된 클래스만 CSS 생성3.2 사용 예시
<div class="flex items-center justify-center h-screen bg-gray-100"> <div class="max-w-md p-6 bg-white rounded-lg shadow-lg"> <h1 class="text-2xl font-bold text-blue-600 mb-4"> Hello World </h1> <button class="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600"> Click me </button> </div> </div>각 클래스는 하나의 CSS 속성에 대응:
- flex → display: flex
- items-center → align-items: center
- bg-blue-500 → background-color: #3b82f6
- p-6 → padding: 1.5rem
3.3 빌드 프로세스
1. 파일 스캔 └─ content에 지정된 파일들을 읽음 2. 클래스 추출 └─ className="..." 안의 Tailwind 클래스 찾기 3. CSS 생성 └─ 사용된 클래스에 해당하는 CSS만 생성 4. 최적화 └─ 중복 제거, 압축예시
입력 (JSX):
<div className="flex items-center bg-blue-500"> <p className="text-white font-bold">Hello</p> </div>추출된 클래스:
- flex, items-center, bg-blue-500, text-white, font-bold
생성된 CSS:
.flex { display: flex; } .items-center { align-items: center; } .bg-blue-500 { background-color: rgb(59 130 246); } .text-white { color: rgb(255 255 255); } .font-bold { font-weight: 700; }3.4 JIT (Just-In-Time) 모드
Tailwind v3부터 기본 방식입니다.
기존 방식 vs JIT
기존 방식:
모든 가능한 클래스 생성 (10-50MB) ↓ 사용되지 않은 클래스 제거 ↓ 최종 CSS (20-50KB)JIT 방식:
파일 스캔 ↓ 사용된 클래스만 즉시 생성 ↓ 최종 CSS (20-50KB)JIT의 장점
임의 값 사용 가능:
<div class="w-[137px]"> <div class="bg-[#1da1f2]">즉시 생성:
.w-\[137px\] { width: 137px; } .bg-\[\#1da1f2\] { background-color: #1da1f2; }빠른 빌드 속도:
- 필요한 것만 생성
- 개발 중에도 즉시 반영
3.5 PostCSS 통합
PostCSS란?
PostCSS는 CSS를 변환하는 도구입니다. JavaScript 플러그인으로 CSS를 처리합니다.
입력 CSS → PostCSS → 플러그인들 → 출력 CSSVite와 함께
// postcss.config.js module.exports = { plugins: { tailwindcss: {}, // @tailwind 디렉티브 처리 autoprefixer: {} // 브라우저 호환성 } }처리 흐름:
1. src/index.css 읽기 @tailwind base; @tailwind utilities; 2. PostCSS의 Tailwind 플러그인 실행 → 실제 CSS로 변환 3. Autoprefixer 실행 → 벤더 프리픽스 추가 4. 최종 CSS 생성PostCSS 없이는 Tailwind가 작동하지 않습니다.
3.6 동적 클래스명 문제
왜 동적 클래스명을 감지 못하는가?
Tailwind는 정적 분석을 사용합니다:
// ✅ 감지 가능 <div className="bg-blue-500"> // ❌ 감지 불가 - 문자열 조합 const color = 'blue' <div className={`bg-${color}-500`}> // ❌ 감지 불가 - 변수 const className = 'bg-blue-500' <div className={className}>이유:
- 빌드 타임에 JavaScript를 실행할 수 없음
- 단순 텍스트 매칭만 가능
해결 방법
1. 전체 클래스명 사용 (권장):
// ❌ 나쁜 예 <div className={`bg-${color}-500`}> // ✅ 좋은 예 const colorClasses = { blue: 'bg-blue-500', red: 'bg-red-500', green: 'bg-green-500' } <div className={colorClasses[color]}>2. Safelist 사용:
// tailwind.config.js module.exports = { safelist: [ 'bg-blue-500', 'bg-red-500', 'bg-green-500' ] }Safelist에 추가하면 파일에 없어도 CSS에 포함됩니다.
3.7 브라우저 전달
프로덕션 모드
<head> <link rel="stylesheet" href="/assets/main.abc123.css"> </head>/* 압축됨 */ .flex{display:flex}.items-center{align-items:center}.bg-blue-500{background-color:rgb(59 130 246)}파일 크기: 20-50KB (gzip 압축 후)
3.8 장단점
✅ 장점
- 빠른 개발 속도: HTML에서 바로 스타일 작성
- 일관된 디자인 시스템: 미리 정의된 값 사용
- 작은 번들 크기: 사용한 클래스만 포함
- JIT로 무한한 유연성: 임의 값 사용 가능
❌ 단점
- HTML 가독성 저하: 클래스가 많아지면 복잡
- 학습 곡선: 모든 유틸리티 클래스 암기 필요
- 동적 스타일 제한: 문자열 조합 불가
- 커스터마이징 복잡성: 기본 디자인에서 벗어나면 복잡
4. Vanilla Extract - Zero-runtime CSS-in-TypeScript
4.1 핵심 개념
Vanilla Extract는 TypeScript로 스타일을 작성하지만, 빌드 타임에 정적 CSS로 변환되는 방식입니다.
전체 처리 과정
개발 단계 빌드 단계 브라우저 ┌─────────┐ ┌──────────┐ ┌─────────┐ │ .css.ts │ 실행 │ Vanilla │ CSS 생성 │ CSSOM │ │ 파일 │ ────→ │ Extract │ ────────→ │ 생성 │ └─────────┘ └──────────┘ └─────────┘다른 라이브러리와의 차이
styled-components: 런타임에 JS로 CSS 생성 Vanilla Extract: 빌드 타임에 TS → CSS 변환4.2 사용 예시
스타일 정의
// Button.css.ts import { style, styleVariants } from '@vanilla-extract/css'; export const button = style({ padding: '10px 20px', borderRadius: '8px', border: 'none', cursor: 'pointer', ':hover': { transform: 'translateY(-2px)' } }); export const buttonVariants = styleVariants({ primary: { backgroundColor: '#3b82f6', color: 'white', }, secondary: { backgroundColor: '#6b7280', color: 'white', } });컴포넌트에서 사용
// Button.tsx import * as styles from './Button.css'; export function Button({ variant = 'primary', children }) { return ( <button className={`${styles.button} ${styles.buttonVariants[variant]}`}> {children} </button> ); }4.3 주요 API
style() - 단일 스타일 생성
export const container = style({ display: 'flex', gap: '16px' });styleVariants() - 여러 변형 생성
export const sizes = styleVariants({ small: { fontSize: '12px' }, medium: { fontSize: '16px' }, large: { fontSize: '20px' } });createTheme() - 테마 생성
export const [themeClass, vars] = createTheme({ color: { primary: '#3b82f6', secondary: '#6b7280' } }); export const button = style({ backgroundColor: vars.color.primary // 타입 안전! });4.4 빌드 프로세스
1. .css.ts 파일 발견 ↓ 2. TypeScript 코드 실행 ↓ 3. CSS 생성 ↓ 4. 클래스명 해싱 ↓ 5. .css.ts를 클래스명 export로 변환 ↓ 6. 정적 CSS 파일 생성변환 과정
입력 (Button.css.ts):
export const button = style({ padding: '10px 20px', backgroundColor: '#3b82f6' });1단계: TypeScript 실행 & 데이터 수집
{ identifier: 'button', styles: { padding: '10px 20px', backgroundColor: '#3b82f6' } }2단계: 클래스명 생성
Button.css.ts + button → button_abc1233단계: CSS 생성
.button_abc123 { padding: 10px 20px; background-color: #3b82f6; }4단계: .css.ts 재작성
// 빌드 후 export const button = 'button_abc123';5단계: 컴포넌트에서 사용
import * as styles from './Button.css'; // styles.button은 'button_abc123' 문자열 <button className={styles.button}>4.5 상태 기반 스타일링
Vanilla Extract는 빌드 타임에 CSS가 생성되므로, 런타임 상태를 직접 사용할 수 없습니다.
방법 1: styleVariants (권장)
// Button.css.ts export const buttonState = styleVariants({ active: { backgroundColor: '#3b82f6', color: 'white' }, inactive: { backgroundColor: '#e5e7eb', color: '#6b7280' }, loading: { backgroundColor: '#93c5fd', opacity: 0.7 } }); // Button.tsx <button className={`${styles.button} ${styles.buttonState[state]}`}> Button </button>방법 2: CSS Variables (완전 동적 값)
// Box.css.ts import { style, createVar } from '@vanilla-extract/css'; export const widthVar = createVar(); export const colorVar = createVar(); export const box = style({ width: widthVar, backgroundColor: colorVar }); // Box.tsx import { assignInlineVars } from '@vanilla-extract/dynamic'; <div className={styles.box} style={assignInlineVars({ [styles.widthVar]: `${width}px`, [styles.colorVar]: color })} />브라우저 렌더링:
<div class="box_abc123" style="--widthVar__xyz: 150px; --colorVar__xyz: #3b82f6" > </div>핵심 원칙
"모든 가능한 스타일을 빌드 시 생성, 런타임에는 선택만"
4.6 브라우저 전달
프로덕션 모드
<head> <link rel="stylesheet" href="/assets/styles.xyz789.css"> </head>/* 압축됨 */ .button_abc123{padding:10px 20px;background-color:#3b82f6;color:#fff}중요:
- TypeScript 코드는 전혀 포함되지 않음
- 런타임에 JavaScript 실행 없음
4.7 장단점
✅ 장점
- Zero Runtime: 브라우저에서 JavaScript 실행 없음
- 완전한 타입 안정성: TypeScript의 모든 기능 활용
- 스코프 격리: 자동으로 고유한 클래스명 생성
- 정적 추출: CSS가 별도 파일로 생성, 캐싱 활용
- 개발 경험: 자동완성, 리팩토링 안전
❌ 단점
- 제한적인 동적 스타일: 런타임 값을 직접 사용 불가
- 빌드 설정 필요: Vite/Webpack 플러그인 설정
- 학습 곡선: 새로운 API 학습 필요
- 디버깅 어려움: 해시된 클래스명
5. 전체 비교 및 선택 가이드
5.1 한눈에 보는 비교표
특징 SCSS Tailwind CSS Vanilla Extract
작성 방식 CSS 확장 문법 HTML 클래스 TypeScript 변환 시점 빌드 타임 빌드 타임 빌드 타임 런타임 비용 없음 없음 없음 타입 안정성 없음 없음 완벽 동적 스타일 불가능 제한적 (Safelist) 제한적 (CSS Variables) 번들 크기 중간 작음 (20-50KB) 작음 (20-50KB) 학습 곡선 낮음 중간 중간 개발 속도 중간 빠름 중간 유지보수 중간 쉬움 쉬움 5.2 처리 방식 비교
SCSS
개발: .scss 파일 작성 ↓ 빌드: CSS로 컴파일 ↓ 브라우저: 일반 CSS 파싱특징:
- CSS 문법의 확장
- 변수, 중첩, 믹스인 등 강력한 기능
- 전역 네임스페이스 문제
Tailwind CSS
개발: HTML에 유틸리티 클래스 작성 ↓ 빌드: 파일 스캔 → 사용된 클래스만 CSS 생성 ↓ 브라우저: 일반 CSS 파싱특징:
- 유틸리티 우선 접근
- HTML을 벗어나지 않고 스타일 작성
- JIT로 임의 값 사용 가능
Vanilla Extract
개발: .css.ts 파일 작성 ↓ 빌드: TypeScript 실행 → CSS 생성 → 클래스명 export ↓ 브라우저: 일반 CSS 파싱특징:
- TypeScript로 타입 안전한 스타일
- Zero runtime (런타임 비용 없음)
- 빌드 타임에 모든 것이 결정
5.3 동적 스타일 처리 비교
SCSS
// ❌ 불가능 .box { width: $dynamicWidth; // 런타임 값 불가 } // ✅ 가능: 미리 정의된 변형 .box-small { width: 100px; } .box-large { width: 200px; }Tailwind CSS
// ❌ 불가능 <div className={`w-${width}`}> // ✅ 가능: 전체 클래스명 const widthClasses = { small: 'w-24', large: 'w-48' } <div className={widthClasses[size]}> // ✅ 가능: Safelist // tailwind.config.js safelist: ['w-24', 'w-48']Vanilla Extract
// ❌ 불가능 export const box = style({ width: props.width // 런타임 값 불가 }); // ✅ 가능: styleVariants export const widthVariants = styleVariants({ small: { width: '100px' }, large: { width: '200px' } }); // ✅ 가능: CSS Variables export const widthVar = createVar(); export const box = style({ width: widthVar }); // 컴포넌트에서 style={assignInlineVars({ [widthVar]: `${width}px` })}5.4 성능 비교
초기 로딩 (First Paint)
SCSS: HTML + CSS (50KB) Tailwind CSS: HTML + CSS (30KB) Vanilla Extract: HTML + CSS (30KB) 모두 빌드 타임 처리로 런타임 오버헤드 없음런타임 성능
SCSS: CSS 파싱만 Tailwind CSS: CSS 파싱만 Vanilla Extract: CSS 파싱만 차이 없음 (모두 정적 CSS)개발 빌드 속도
SCSS: 빠름 (간단한 컴파일) Tailwind CSS: 매우 빠름 (JIT) Vanilla Extract: 중간 (TypeScript 실행)6. 결론
6.1 핵심 요약
브라우저 관점
모든 방식이 결국 같은 결과:
SCSS로 작성 → CSS 변환 → 브라우저는 CSS만 봄 Tailwind로 작성 → CSS 변환 → 브라우저는 CSS만 봄 VE로 작성 → CSS 변환 → 브라우저는 CSS만 봄브라우저는 어떤 도구를 사용했는지 전혀 모릅니다. 모두 동일한 렌더링 과정을 거칩니다:
CSS 파싱 → CSSOM → Render Tree → Layout → Paint → Composite개발자 관점
차이는 개발 경험과 유지보수성:
- SCSS: CSS를 더 편하게 작성
- Tailwind: 빠르고 일관된 개발
- Vanilla Extract: 타입 안전과 성능 최적화
6.2 선택 결정 트리
타입 안정성이 최우선인가? ├─ Yes → Vanilla Extract └─ No ├─ 빠른 개발이 중요한가? │ ├─ Yes → Tailwind CSS │ └─ No │ ├─ 기존 CSS 지식을 활용하고 싶은가? │ │ ├─ Yes → SCSS │ │ └─ No → Tailwind CSS │ └─ └─