Vanilla Extract란?
Zero-runtime CSS in JS 라이브러리.
CSS-in-JS는 말 그대로 "JavaScript 안에서 CSS를 작성하는 방식"이다.
즉, 스타일을 별도의 CSS 파일이 아니라 JavaScript 코드 안에서 작성하는 것.
대표적인 CSS-in-JS 라이브러리로는 Styled-Components, Emotion, JSS 등이 있다.
CSS-in-JS의 장점
✅ 컴포넌트와 스타일을 함께 관리할 수 있음
웹페이지의 구성 요소(컴포넌트)와 스타일을 하나의 파일 안에서 함께 작성할 수 있어 유지보수가 쉽다.
스타일이 특정 컴포넌트에 종속되므로 다른 곳에서 스타일이 섞이는 일이 줄어든다.
✅ 동적으로 스타일을 변경하기 쉬움
JavaScript 변수를 활용해 색상, 크기, 여백 등의 값을 변경할 수 있다.
예를 들어, 버튼의 색상을 사용자 설정에 맞게 변경하는 것이 가능하다.
✅ 사용하지 않는 스타일이 자동 정리됨
전통적인 CSS 방식에서는 필요 없는 스타일이 파일에 남아 있는 경우가 많은데, CSS-in-JS는 사용하지 않는 스타일을 자동으로 제거하여 코드 최적화가 가능
CSS-in-JS의 단점
❌ 초기 로딩 속도가 느릴 수 있음
스타일이 JavaScript 코드 안에서 실행되므로, 브라우저가 화면을 표시하는 데 시간이 조금 더 걸릴 수 있다.
❌ 클래스 이름이 직관적이지 않음
CSS-in-JS에서는 자동으로 클래스 이름이 생성되는데, 난해한 이름이 될 수 있어 디버깅이 어렵다.
❌ 브라우저에서 스타일을 적용하는 데 시간이 더 걸릴 수 있음
CSS 파일을 미리 읽어오는 전통적인 방식보다, JavaScript가 실행되면서 스타일이 적용되는 방식이기 때문에 성능에 부담이 될 수 있다.
런타임 CSS in JS는 런타임에 js파일이 실행되면서 style을 생성한다. style 생성의 규모가 크고 빈번할 수록 성능이 저하된다.
Runtime CSS in JS의 문제점을 해결하기 위해 Zero-runtime CSS in JS가 등장한다. Linaria, Sttiches, Vanilla Extract등이 있다.
Vanilla Extract의 특징
- CSS Modules-inTypeScript
- 제로 런타임 (Zero-runtime): 기존 CSS-in-JS 라이브러리와 달리, 런타임에 JavaScript로 스타일을 생성하지 않는다. 모든 스타일은 빌드 과정에서 정적 CSS 파일로 추출된다. 이로 인해 런타임 성능이 향상되고 서버 사이드 렌더링(SSR) 시 Hydration 에러를 방지할 수 있다.
- 타입스크립트 지원 (Type-safe): 스타일을 TypeScript 코드로 작성하므로, CSS 속성 이름이나 값에 대한 오타를 컴파일 시점에 확인할 수 있다. 강력한 타입 추론 및 자동 완성을 제공
- 로컬 스코프 (Locally Scoped): CSS Modules처럼 기본적으로 클래스 이름에 고유한 해시값을 부여하여 로컬 스코프를 적용. 이로 인해 클래스 이름 충돌을 걱정할 필요가 없다.
- 표준 CSS와 유사한 작성 방식: JavaScript 객체 형태로 스타일을 정의하지만, 일반적인 CSS 작성 방식과 문법이 매우 유사하여 러닝 커브가 낮다. display: 'flex'처럼 카멜 케이스(camelCase)를 사용
- 강력한 테마 시스템 (Theming): 타입 안전성이 보장되는 디자인 토큰(CSS 변수)을 활용하여 전역 또는 다중 테마를 쉽게 생성하고 관리
- 프레임워크 agnostic: 특정 프레임워크에 종속되지 않으며, 웹팩, esbuild, Vite, Next.js 등 다양한 빌드 도구 및 프레임워크와 공식적인 통합을 제공
- 동적 스타일링 지원 (CSS 변수 활용): props 기반의 동적 스타일링은 어렵지만, CSS 변수(Variables)와 Sprinkles와 같은 유틸리티 API를 통해 동적인 스타일 변화를 지원
Vanilla Extract의 사용법
import { createGlobalTheme, style } from "@vanilla-extract/css";
export const vars = createGlobalTheme(":root", {
color: {
main: "#ffa726",
mainDarker: "#f57c00",
mainFaded: "#ffb74d",
mainFadedBright: "#ffb74da6",
list: "rgb(235, 236, 240)",
task: "rgb(255, 255, 255)",
taskHover: "rgb(245, 245, 245)",
brightText: "rgb(255, 255, 255)",
darkText: "rgb(24, 42, 77)",
seconarDdarkText: "rgb(94, 108, 132)",
seconarDdarkTextHover: "rgb(218, 219, 226)",
selectedTab: "rgb(137, 176, 174)",
updateButton: "rgb(237, 180, 88)",
deleteButton: "rgb(237, 51, 88)",
},
fontSizing: {
T1: "32px",
T2: "24px",
T3: "18px",
T4: "14px",
P1: "12px",
},
spacing: {
small: "5px",
medium: "10px",
big1: "20px",
big2: "15px",
listSpacing: "30px"
},
font: {
body: "arial",
},
shadow: {
basic: "4px 4px 8px 0px rgba(34, 60, 80, 0.2)"
},
minWidth: {
list: '250px'
}
})
createGlobalTheme은 vanilla-extract에서 전역 CSS 변수(custom properties)를 정의하는 데 사용되는 핵심 함수.
주로 :root와 같은 전역 셀렉터에 테마 변수를 설정하여 애플리케이션 전체에서 사용할 수 있도록 한다.
:root {
--color-brand__z05zdf0: blue;
--color-accent__z05zdf0: honeydew;
--font-body__z05zdf1: arial;
}
빌드 시 위와 유사한 정적 CSS가 생성된다. 변수 이름은 기본적으로 고유성을 보장하기 위해 해시(예: __z05zdf0)가 추가됨
변수 사용법
생성된 vars 객체를 다른 vanilla-extract 스타일시트에서 가져와 타입 안전하게 사용할 수 있다.
import { style } from "@vanilla-extract/css";
import { vars } from "../../App.css";
export const container = style({
minHeight: 'max-content',
padding: vars.spacing.big2,
backgroundColor: vars.color.mainDarker
})