디자인 도구에서 정한 색과 간격이 코드 곳곳에 날값으로 흩어지면, 화면과 코드는 시간이 지날수록 서로 다른 얼굴을 하게 됩니다. 디자인 값을 한 벌의 CSS 변수로 옮기고 컴포넌트가 그 변수만 바라보게 만들면 둘이 자동으로 맞물리는데, 그 과정을 코드로 보여 드리겠습니다.
42.1 흩어진 하드코딩의 문제
같은 브랜드 색이 여러 파일 여러 규칙에 날값으로 박혀 있으면, 그 색을 바꿀 때 온 코드를 뒤져야 합니다. 아래는 흔히 마주치는 모습입니다.
.btn {
background: #2563eb;
padding: 12px 16px;
}
.link {
color: #2563eb;
}
.badge {
background: #2563eb;
border-radius: 6px;
}
.header {
border-bottom: 1px solid #e5e7eb;
padding: 16px;
}
#2563eb가 세 군데, 16px가 두 군데에 흩어져 있습니다. 브랜드 색이 청록으로 바뀌면 세 곳을 빠짐없이 찾아 고쳐야 하고, 하나라도 놓치면 화면 어딘가에 옛 파랑이 남아 디자인과 어긋납니다.
42.2 값을 한 곳으로 추출하기
디자인 도구에서 정한 색과 간격을 먼저 한자리에 모아 이름을 붙입니다. 이 :root가 화면 전체의 유일한 출처가 됩니다.
:root {
--color-brand: #2563eb;
--color-border: #e5e7eb;
--space-4: 16px;
--radius: 6px;
}
이제 화면에 쓰이는 값이 어디서 왔는지 이 한 곳만 보면 됩니다. 디자인 도구의 값과 코드의 값이 이 표에서 정확히 만납니다.
42.3 컴포넌트가 변수만 참조하게
날값을 지우고 컴포넌트가 오직 변수만 바라보게 바꿉니다. 값을 직접 아는 컴포넌트는 하나도 없어야 합니다.
.btn {
background: var(--color-brand);
padding: 12px var(--space-4);
}
.link {
color: var(--color-brand);
}
.badge {
background: var(--color-brand);
border-radius: var(--radius);
}
.header {
border-bottom: 1px solid var(--color-border);
padding: var(--space-4);
}
컴포넌트는 이제 무슨 색인지 모른 채 그저 브랜드 색을 달라고 요청할 뿐입니다. 실제 값을 결정하는 권한은 :root 한 곳에만 있습니다.
42.4 값 하나로 전체 동기화
브랜드 색을 바꿔야 할 때, 이제 고칠 곳은 단 한 줄입니다. 여러 파일을 뒤질 일이 없습니다.
:root {
--color-brand: #0d9488;
}
이 한 줄을 고치는 순간 버튼과 링크와 배지가 한꺼번에 청록으로 바뀝니다. 물감통 하나를 바꾸니 그 물감을 쓰던 붓들이 전부 새 색으로 칠하는 셈입니다. 디자인 도구에서 색을 바꾸고 코드에서 변수 하나를 맞추면 두 세계가 곧바로 같은 얼굴이 됩니다.
42.5 간격 스케일을 변수로
디자인 도구는 대개 4의 배수 같은 간격 눈금 위에서 움직입니다. 그 눈금을 그대로 변수 스케일로 옮겨 두면 코드도 같은 리듬을 따릅니다.
:root {
--space-1: 4px;
--space-2: 8px;
--space-3: 12px;
--space-4: 16px;
--space-6: 24px;
}
.card {
padding: var(--space-6);
gap: var(--space-2);
}
디자인 도구가 8과 24를 쓰면 코드도 --space-2와 --space-6을 씁니다. 눈금 밖의 15px 같은 어중간한 값이 슬쩍 끼어들 틈이 없어, 화면 전체의 간격이 한 리듬으로 정돈됩니다.
42.6 타이포도 같은 방식으로
글꼴과 글자 크기, 줄 간격도 색이나 간격과 똑같이 변수로 모읍니다. 타이포만 따로 흩어 두면 결국 같은 문제가 되풀이됩니다.
:root {
--font-sans: system-ui, sans-serif;
--font-size-sm: 13px;
--font-size-base: 15px;
--line-tight: 1.3;
}
.body-text {
font-family: var(--font-sans);
font-size: var(--font-size-base);
line-height: var(--line-tight);
}
기본 글자 크기를 15px에서 16px로 올리고 싶으면 변수 하나만 고치면 됩니다. 본문을 쓰는 모든 요소가 새 크기로 함께 자랍니다.
42.7 원시 토큰과 의미 토큰 두 겹
디자인 도구의 색 팔레트를 그대로 옮긴 원시 토큰 위에, 그 쓰임을 가리키는 의미 토큰을 한 겹 더 얹으면 동기화가 한결 매끄러워집니다. 컴포넌트는 의미 토큰만 바라봅니다.
:root {
/* 원시 토큰: 팔레트 그대로 */
--blue-500: #2563eb;
--blue-600: #1d4ed8;
/* 의미 토큰: 원시 토큰을 가리킴 */
--color-action: var(--blue-500);
--color-action-hover: var(--blue-600);
}
.btn--primary {
background: var(--color-action);
}
.btn--primary:hover {
background: var(--color-action-hover);
}
마크업 쪽은 그저 클래스를 붙일 뿐, 어떤 색이 나오는지 알 필요가 없습니다.
<button class="btn btn--primary">결제</button>
디자인에서 주요 행동 색을 파랑에서 청록으로 바꾸면 --color-action이 가리키는 원시 토큰만 갈아 끼우면 됩니다. 팔레트와 쓰임이 두 겹으로 나뉘어 있어, 색을 새로 추가하는 일과 그 색을 어디에 쓰는지 정하는 일이 서로를 방해하지 않습니다.
42.8 한 벌의 변수로 테마까지
변수를 유일한 출처로 두면 테마 전환마저 값 교체 한 번으로 끝납니다. 컴포넌트는 손대지 않고 :root의 값만 갈아 끼웁니다.
:root {
--color-bg: #ffffff;
--color-text: #111827;
}
[data-theme="dark"] {
--color-bg: #0b1220;
--color-text: #e5e7eb;
}
body {
background: var(--color-bg);
color: var(--color-text);
}
어두운 테마에서는 같은 변수에 다른 값이 들어갈 뿐, body의 코드는 그대로입니다. 컴포넌트가 변수만 바라보기에, 값을 결정하는 :root만 바꾸면 화면 전체가 한 번에 갈아입습니다.
42.9 정리
디자인과 코드의 동기화는 거창한 장치가 아니라, 값을 한 곳에 모으고 컴포넌트가 그곳만 바라보게 하는 습관에서 옵니다. 날값을 :root의 변수로 걷어 올리는 순간, 디자인 도구에서 값 하나를 바꾸는 일과 코드에서 변수 하나를 고치는 일이 같은 무게가 됩니다. 두 세계가 같은 출처를 공유하면 어긋날 여지 자체가 사라집니다.