피그마의 색 스타일과 변수는 코드로 옮기면 CSS 커스텀 프로퍼티, 곧 토큰이 됩니다. 오늘은 흩어진 값들을 :root에 모으고, 원시 토큰과 의미 토큰 두 단계로 나눠 다뤄 보겠습니다.

65.1 흩어진 값을 :root로 모읍니다

버튼마다 파랑을 직접 적어 두면, 색을 바꿀 때 모든 규칙을 뒤져야 합니다. 값을 한 곳에 이름으로 정의해 두고 var()로 꺼내 쓰는 것이 토큰의 출발입니다.


:root {
--blue-500: #2563eb;
}
.btn {
background: var(--blue-500);
}
.link {
color: var(--blue-500);
}


이제 파랑을 바꾸고 싶으면 --blue-500 한 줄만 고칩니다. 버튼과 링크가 같은 토큰을 참조하니 한꺼번에 따라 바뀝니다.

65.2 원시 토큰과 의미 토큰 두 단계

피그마에서 색 팔레트와 그 색의 쓰임을 나눠 두듯, 토큰도 두 층으로 나눕니다. 원시 토큰은 색 그 자체에 이름을 붙이고, 의미 토큰은 그 색이 무슨 역할인지를 가리킵니다.


:root {
--blue-500: #2563eb;
--blue-600: #1d4ed8;
--gray-900: #111827;

--color-primary: var(--blue-500);
--color-primary-hover: var(--blue-600);
--color-text: var(--gray-900);
}


규칙은 원시 토큰이 아니라 의미 토큰만 바라봅니다. 그래야 팔레트가 바뀌어도 규칙을 건드릴 일이 없습니다.


.btn--primary {
background: var(--color-primary);
color: #fff;
}
.btn--primary:hover {
background: var(--color-primary-hover);
}


브랜드 색을 초록으로 바꾸고 싶다면 --color-primary가 가리키는 원시 토큰만 --green-500로 돌리면 됩니다. 버튼 규칙은 자기가 primary를 쓴다는 사실만 알 뿐, 그게 무슨 색인지는 신경 쓰지 않습니다.

65.3 간격도 스케일 토큰으로

여백을 20px, 24px처럼 그때그때 적으면 화면마다 리듬이 어긋납니다. 피그마의 간격 변수처럼, 정해진 단계만 토큰으로 두고 그중에서 고르게 합니다.


:root {
--space-1: 4px;
--space-2: 8px;
--space-3: 16px;
--space-4: 24px;
--space-5: 32px;
}


규칙에서는 숫자 대신 단계를 부릅니다. 그러면 간격이 늘 4의 배수 리듬 위에 놓입니다.


.card {
padding: var(--space-4);
}
.card__title {
margin-bottom: var(--space-2);
}
.card__body + .card__footer {
margin-top: var(--space-3);
}


단계에서 고르니 어림짐작한 23px 같은 값이 끼어들 틈이 없습니다. 리듬이 흐트러지지 않는 것이 스케일 토큰의 값어치입니다.

65.4 타이포와 반경도 토큰으로

글자 크기와 모서리 반경도 같은 방식으로 묶습니다. 자주 쓰는 값에 이름을 붙여 두면, 화면 전체가 같은 규격을 공유합니다.


:root {
--font-sm: 13px;
--font-base: 15px;
--font-lg: 20px;
--radius-sm: 6px;
--radius-md: 10px;
}


소비하는 쪽은 언제나 var()로 값을 끌어다 씁니다.


.card {
border-radius: var(--radius-md);
font-size: var(--font-base);
}
.badge {
border-radius: var(--radius-sm);
font-size: var(--font-sm);
}


반경을 조금 더 둥글게 바꾸고 싶으면 --radius-md 한 줄이면 끝입니다. 카드든 팝업이든 그 토큰을 쓰는 모든 요소가 함께 둥글어집니다.

65.5 다크 모드는 토큰만 갈아 끼웁니다

토큰의 진가는 테마를 바꿀 때 드러납니다. 규칙은 그대로 두고 의미 토큰이 가리키는 값만 다른 팔레트로 돌리면, 화면 전체가 다크 모드로 넘어갑니다.


:root {
--color-bg: #ffffff;
--color-text: #111827;
--color-primary: var(--blue-500);
}
[data-theme="dark"] {
--color-bg: #0f172a;
--color-text: #e2e8f0;
--color-primary: var(--blue-400);
}


버튼이나 카드 규칙은 한 글자도 바뀌지 않습니다. 이미 의미 토큰만 바라보고 있으니까요.


body {
background: var(--color-bg);
color: var(--color-text);
}


루트에 data-theme만 바꿔 달면 한순간에 낮과 밤이 뒤바뀝니다. 토큰이라는 한 겹의 이름 덕분에, 색을 쓰는 곳이 아니라 색을 정의하는 곳만 손대면 되는 것입니다.

65.6 토큰 하나로 전체가 움직입니다

토큰을 잘 세워 두면 디자인 결정 하나가 코드 한 줄로 끝납니다. 브랜드 파랑을 보라로 옮기라는 요청이 와도, 규칙을 뒤질 필요 없이 원시 토큰만 바꿉니다.


:root {
--blue-500: #7c3aed;
}


이 한 줄로 primary를 쓰던 모든 버튼과 링크, hover 색까지 줄줄이 보라로 바뀝니다. 값을 쓰는 곳과 정의하는 곳을 갈라 둔 대가가, 바로 이 한 줄짜리 변경입니다.

65.7 컴포넌트 토큰으로 한 겹 더

버튼처럼 자주 쓰는 부품은 자기만의 토큰을 한 겹 더 두면 다루기 편합니다. 의미 토큰을 다시 받아 컴포넌트 토큰에 담고, 규칙은 그 컴포넌트 토큰만 소비하게 합니다.


.btn {
--btn-bg: var(--color-primary);
--btn-fg: #fff;
background: var(--btn-bg);
color: var(--btn-fg);
}
.btn--ghost {
--btn-bg: transparent;
--btn-fg: var(--color-primary);
border: 1px solid var(--color-primary);
}


변형은 색을 직접 적지 않고 --btn-bg 값만 바꿔 끼웁니다. 배경과 글자를 칠하는 규칙은 base에 한 번만 있으니, 변형이 늘어도 칠하는 코드는 그대로입니다.


<button class="btn">기본</button>
<button class="btn btn--ghost">테두리</button>


원시, 의미, 컴포넌트로 이어지는 세 겹의 이름이 층층이 쌓이니, 어느 층을 바꾸느냐에 따라 영향 범위를 정확히 고를 수 있습니다.

65.8 정리

피그마 스타일과 변수는 :root의 커스텀 프로퍼티로, 팔레트는 원시 토큰으로, 쓰임은 의미 토큰으로 옮겨집니다. 규칙은 언제나 var()로 의미 토큰만 소비하고, 다크 모드나 브랜드 변경은 정의하는 토큰만 갈아 끼웁니다. 값이 흩어지지 않고 한 이름 아래 모여 있을 때, 디자인 변경은 언제나 한 줄에서 시작됩니다.