디자인 시스템을 오래 굴리다 보면 시스템 자체가 발목을 잡는 순간이 옵니다. 대개는 큰 사고가 아니라 작은 편법이 조용히 쌓여서 생기죠. 자주 밟는 함정을 나쁜 코드와 고친 코드로 나란히 놓아 보겠습니다.
46.1 쓰지도 않을 토큰을 미리 잔뜩
시작부터 완벽한 스케일을 갖추겠다고 토큰을 수십 개 찍어 두면, 절반은 끝내 아무도 부르지 않습니다. 안 쓰는 토큰은 고를 때 눈만 어지럽히는 잡음입니다.
:root {
--space-1: 2px;
--space-2: 4px;
--space-3: 6px;
--space-4: 8px;
--space-5: 10px;
/* ... --space-40: 160px 까지 선언 */
/* 실제로는 대여섯 개만 쓰임 */
}
화면이 실제로 되풀이해 부르는 단계만 남기면, 목록이 짧아지고 어떤 값을 써야 할지 헷갈릴 일도 사라집니다.
:root {
--space-xs: 4px;
--space-sm: 8px;
--space-md: 16px;
--space-lg: 24px;
--space-xl: 40px;
}
다섯 칸으로 줄었지만 실제 레이아웃은 이걸로 거의 다 덮입니다. 부족해지면 그때 한 칸 더 늘리면 됩니다.
46.2 뜻이 안 보이는 이름
토큰 이름에 순번만 붙이면, 정작 그 색이 무슨 역할인지는 아무도 모릅니다. 이름은 값이 아니라 쓰임을 가리켜야 합니다.
:root {
--blue1: #eff6ff;
--blue2: #2563eb;
--blue3: #1e40af;
}
.btn { background: var(--blue2); } /* blue2가 뭐지? */
역할로 이름을 붙이면 코드만 읽어도 의도가 드러납니다.
:root {
--color-primary: #2563eb;
--color-primary-strong: #1e40af;
--color-primary-surface: #eff6ff;
}
.btn { background: var(--color-primary); }
브랜드 파랑을 초록으로 바꿔도 이름은 그대로 primary라 손댈 곳이 헷갈리지 않습니다.
46.3 시맨틱 층을 건너뛰기
컴포넌트가 원시 팔레트 색을 직접 부르면, 브랜드색 하나 바꿀 때 온 파일을 뒤져야 합니다. 원시색과 컴포넌트 사이에 의미 토큰 한 겹을 끼우세요.
.btn { background: var(--blue-500); }
.link { color: var(--blue-500); }
.badge { background: var(--blue-500); }
/* 강조색 교체 = 여기저기 다 찾아 수정 */
의미 토큰 하나를 두면 교체 지점이 한 줄로 모입니다.
:root { --color-accent: var(--blue-500); }
.btn { background: var(--color-accent); }
.link { color: var(--color-accent); }
.badge { background: var(--color-accent); }
이제 --color-accent 한 줄만 바꾸면 버튼도 링크도 배지도 한꺼번에 따라옵니다. 원시색은 팔레트로, 컴포넌트는 의미 토큰으로, 이렇게 두 층을 갈라 두는 습관 하나가 나중의 대대적인 수정을 막아 줍니다.
46.4 !important 로 시스템 우회
급하다고 !important를 박으면 그 순간은 이기지만, 다음 사람은 그걸 이기려 또 !important를 씁니다. 힘겨루기가 끝나지 않습니다.
.btn--danger { background: red !important; }
.modal .btn { background: blue !important; }
/* 우회가 우회를 부름 */
변형은 지역 변수를 바꾸는 식으로 풀면 명시도 싸움 없이 깔끔합니다.
.btn { background: var(--btn-bg, var(--color-primary)); }
.btn--danger { --btn-bg: var(--color-danger); }
덮어쓰는 게 아니라 재료만 갈아 끼우니 !important가 필요 없습니다.
46.5 하드코딩 예외가 쌓임
화면마다 눈대중으로 값을 조금씩 다르게 주면, 나중엔 왜 17px인지 아무도 설명하지 못합니다. 근거 없는 숫자는 유지보수의 지뢰입니다.
.card-a { padding: 17px; }
.card-b { padding: 13px; }
.card-c { padding: 21px; }
스케일 토큰으로 통일하면 값에 이유가 생기고 화면이 한 리듬으로 정돈됩니다.
.card { padding: var(--space-md); }
정말 예외가 필요한 카드만 modifier로 따로 열어 두면 규칙과 예외가 뒤섞이지 않습니다.
46.6 다크모드에서 안 바뀌는 색
컴포넌트에 색을 직접 박아 두면, 테마를 바꿀 때 그 색만 옛날 그대로 남아 어색하게 튑니다. 색은 토큰으로 빼고 테마는 토큰만 갈아 끼워야 합니다.
.panel {
background: #ffffff;
color: #111827;
}
[data-theme="dark"] .panel {
background: #1f2937; /* 또 하드코딩 */
color: #f9fafb;
}
토큰만 오버라이드하면 컴포넌트 CSS는 한 벌로 두 테마를 다 받습니다.
:root {
--surface: #ffffff;
--text: #111827;
}
[data-theme="dark"] {
--surface: #1f2937;
--text: #f9fafb;
}
.panel {
background: var(--surface);
color: var(--text);
}
새 컴포넌트도 var(--surface)만 쓰면 다크모드 대응이 공짜로 따라옵니다.
46.7 너무 이른 추상화
한 번밖에 안 쓰는데 변형을 미리 다 열어 두면, 쓰지도 않을 조합을 관리하느라 지칩니다. 추상화는 되풀이가 눈에 보인 뒤에 해도 늦지 않습니다.
.btn--xs { --btn-pad: 2px 6px; }
.btn--sm { --btn-pad: 4px 10px; }
.btn--md { --btn-pad: 8px 16px; }
.btn--lg { --btn-pad: 12px 22px; }
.btn--xl { --btn-pad: 16px 28px; }
/* 실제로는 sm, md 두 개만 쓰임 */
지금 정말 쓰는 것만 남기고, 세 번째로 같은 요구가 오면 그때 한 칸 더 엽니다.
.btn { padding: var(--btn-pad, 8px 16px); }
.btn--sm { --btn-pad: 4px 10px; }
덜 만든 시스템이 안 쓰는 걸 잔뜩 짊어진 시스템보다 다루기 훨씬 가볍습니다.
46.8 정리
함정은 하나같이 편함이라는 이름으로 원칙을 잠깐 건너뛸 때 생깁니다. 안 쓸 토큰을 미리 짓지 않기, 이름은 값이 아니라 역할로, 의미 토큰 한 겹 지키기, !important 대신 변수 교체, 근거 없는 숫자 안 남기기, 색은 토큰으로 빼서 테마에 맡기기, 추상화는 되풀이를 본 뒤에. 이 일곱만 지켜도 시스템이 스스로 발을 거는 일은 크게 줄어듭니다. 시스템은 한 번에 완성되는 건축물이 아니라 계속 돌보며 가꾸는 정원이니까요.