이 글은 STOCAT 프로젝트에서 디자인 시스템 토큰을 정리하면서 발견한 코드 개선 경험을 기록한 글이다.
들어가며
STOCAT 프로젝트에 vanilla-extract로 디자인 토큰 체계를 새로 구축하는 작업을 했다.
색상 토큰(color.role.css.ts)과 타이포그래피 토큰(typography.css.ts)을 정의하고,
기존 컴포넌트들에 순차적으로 적용하는 작업이었다.
그런데 적용하다 보니 반복되는 패턴이 눈에 들어왔다.
// 이게 7개 파일에 걸쳐, 최대 5군데에서 반복되고 있었다
fontSize: vars.typography.body3.fontSize,
fontWeight: vars.typography.body3.fontWeight,
lineHeight: vars.typography.body3.lineHeight,
뭔가 잘못된 느낌이 들었다. 토큰을 만든 이유가 반복을 줄이기 위해서인데,
막상 쓰는 쪽에서 3줄씩 꺼내 쓰고 있으니 의미가 반감되는 것 같았다.
문제 파악
타이포그래피 토큰 정의를 다시 들여다봤다.
// src/shared/styles/typography.css.ts
export const typography = {
h1: { fontSize: "24px", fontWeight: "700", lineHeight: "150%" },
body2: { fontSize: "16px", fontWeight: "500", lineHeight: "150%" },
body3: { fontSize: "14px", fontWeight: "700", lineHeight: "150%" },
// ...
} as const;
각 토큰은 fontSize, fontWeight, lineHeight 세 속성을 묶은 객체다.
그리고 vanilla-extract의 createTheme을 통해 CSS 변수로 변환된 이후에도 구조는 그대로 유지된다.
// src/shared/styles/vars.css.ts
export const [themeClass, vars] = createTheme({
color: { ... },
typography, // <- 그대로 주입
});
vars.typography.body3은 단일 값이 아니라 { fontSize, fontWeight, lineHeight } 구조의 객체다.
그렇다면 spread 연산자로 한 번에 풀 수 있어야 한다.
시도 : 스프레드 연산자 적용
// 변경 전
export const currentBalance = style({
fontSize: vars.typography.h1.fontSize,
fontWeight: vars.typography.h1.fontWeight,
lineHeight: vars.typography.h1.lineHeight,
color: vars.color.role.text,
});
// 변경 후
export const currentBalance = style({
...vars.typography.h1,
color: vars.color.role.text,
});
style() 함수에 전달하는 객체 안에서 스프레드가 동작한다면,
세 속성이 한꺼번에 전개되어 vanilla-extract가 정상적으로 처리할 수 있을 것이다.
vanilla-extract의 style() 함수는 일반 JS 객체를 인자로 받기 때문에, 스프레드 연산자가 완벽하게 동작한다.
빌드를 돌렸다.
pnpm build
✓ 195 modules transformed.
✓ built in 790ms
에러 없이 통과했다.
적용 범위
7개 파일, 총 12군데에 적용했다.
| 파일 | 대상 스타일 | 적용 토큰 |
|---|---|---|
HomeGreetingSection.css.ts |
subTitle |
caption1 |
HomeBanner.css.ts |
title |
body1 |
HomeWallet.css.ts |
currentTime |
caption1 |
HomeWallet.css.ts |
currency |
body5 |
HomeWallet.css.ts |
balance |
body3 |
HomeMarketSection.css.ts |
sectionTitle |
body3 |
MarketCard.css.ts |
name |
body3 |
HomeStockSection.css.ts |
currentBalance |
h1 |
HomeStockSection.css.ts |
currentVariation |
body2 |
MyStockContent.css.ts |
corpInfoTitle |
body3 |
MyStockContent.css.ts |
averagePrice |
body4 |
MyStockContent.css.ts |
currentPrice |
body3 |
MyStockContent.css.ts |
increase / decrease |
body4 |
diff 기준으로 53줄 삭제, 35줄 추가. 코드량이 줄면서 읽기도 훨씬 편해졌다.
왜 처음부터 이렇게 안 했을까?
솔직히 말하면, Vanila-Extract를 잘 몰라서.... 토큰을 처음 적용할 때는 "일단 동작하게" 하는 데 집중했다.
그 과정에서 각 속성을 하나씩 꺼내 쓰는 패턴이 자연스럽게 굳어졌다.
토큰의 구조를 충분히 생각하지 않은 것이 근본 원인이었다.
vars.typography.body3이 객체라는 걸 알면서도, 스프레드로 전개할 수 있다는 발상으로 이어지지 않았다.
배운 것
토큰을 { fontSize, fontWeight, lineHeight } 객체로 묶어서 정의한 건,
관련 속성을 한 단위로 다루기 위해서다. 그렇다면 사용하는 쪽에서도 그 단위를 그대로 활용해야 한다.
스프레드는 그 의도를 가장 자연스럽게 표현하는 방법이었다.
마무리
작은 리팩터링이었지만, 덕분에 "토큰을 왜 객체로 묶어서 정의했는가"를 다시 한번 생각해보게 됐다.
정의와 사용이 같은 논리 단위로 맞아떨어지면, 코드를 읽는 사람이 훨씬 빠르게 의도를 파악할 수 있다.
이 경험을 바탕으로 앞으로 새로운 컴포넌트에 토큰을 적용할 때는 처음부터 스프레드 패턴 구조도 고려해볼 수 있을 것 같다.
'개발 일지 > FrontEnd_프론트엔드' 카테고리의 다른 글
| [STOCAT] vanilla-extract에서 CVA 패턴 쓰기 : @vanilla-extract/recipes 도입기 (0) | 2026.03.03 |
|---|---|
| [STOCAT] Tailwind CSS만 쓰던 나의 vanilla-extract 첫 도전기 (2) | 2026.02.26 |
