
색상 코드를 Ctrl+F로 하나씩 뒤지며 교체하다가 결국 배포 후에 버그를 발견한 적 있으신지요. 저는 있습니다. 그것도 클라이언트 블로그에서요. 그 사건 이후 CSS 변수를 모든 스킨 작업에 기본으로 도입하기 시작했습니다. 써보기 전과 후의 체감 차이가 생각보다 컸습니다.
그런데 사실 이 이야기를 하기 전에 먼저 짚고 싶은 게 있습니다. 그 버그는 단순히 색상 코드를 놓쳐서 생긴 문제가 아니었습니다. 근본적으로는 같은 값이 코드 곳곳에 흩어져 있던 구조 자체가 문제였습니다. CSS 변수는 그 구조적 문제를 해결하는 도구입니다. 색상 관리 편의성만으로 설명하면 반쪽짜리 이해가 됩니다.
커스텀 프로퍼티, 실제로 어떻게 동작하는가
CSS 변수는 공식 명칭으로 커스텀 프로퍼티(Custom Properties)라고 부릅니다. 커스텀 프로퍼티란 개발자가 직접 이름을 정해 값을 저장하고, 필요한 곳 어디서든 꺼내 쓸 수 있는 CSS 고유의 변수 시스템을 의미합니다. 변수 이름 앞에 하이픈 두 개를 붙이고, :root 선택자 안에 선언하면 페이지 전체 어디서나 접근할 수 있습니다. 값을 꺼낼 때는 var() 함수를 사용합니다.
일반적으로 CSS 변수를 색상 관리용으로만 쓰는 도구라고 이해하는 분들이 있는데, 저는 처음에 그렇게 이해하다가 범위를 점차 넓혔습니다. 폰트 크기, 여백, 모서리 반경 같은 레이아웃 수치도 변수로 묶어두면 한 줄 수정으로 전체 디자인 시스템이 움직입니다. 여기서 개인적으로 드는 생각은, 이걸 "색상 변수"라고 부르는 것 자체가 이미 개념을 좁혀버리는 표현이라는 겁니다. 디자인 시스템의 설정값들을 한 곳에 모아두는 구조라고 이해하는 편이 훨씬 넓게 활용할 수 있습니다.
var() 함수에는 폴백(fallback) 값이라는 기능이 있습니다. 폴백이란 변수가 정의되지 않았거나 잘못된 경우에 대신 적용되는 기본값입니다. 팀원이 변수 이름을 오타로 잘못 쓰더라도 폴백 값 덕분에 레이아웃이 통째로 무너지지 않아서 디버깅 시간이 눈에 띄게 줄었습니다. 다만 폴백 값이 오히려 문제를 감춰버릴 수도 있다는 점은 주의해야 합니다. 오타가 있는데도 눈에 안 보이는 상태로 통과되면, 나중에 폴백 값이 없는 환경에서 갑자기 부서지는 상황이 생길 수 있습니다. 폴백은 안전망이지 면죄부가 아닙니다.
MDN Web Docs에 따르면 CSS 커스텀 프로퍼티는 캐스케이드(cascade) 규칙을 그대로 따르기 때문에, 상위 선택자에서 선언한 변수 값을 하위 요소가 자동으로 상속받습니다. 여기서 캐스케이드란 CSS에서 규칙이 충돌할 때 어떤 값을 우선 적용할지 결정하는 계층 구조를 말합니다. 이 원리 덕분에 특정 컴포넌트 안에서만 변수를 재선언하면, 그 안에서만 색상이나 크기가 달라지는 스코프 제어도 가능합니다.
CSS 변수를 JavaScript의 getComputedStyle과 setProperty로 읽고 쓸 수 있다는 점도 처음에는 반신반의했습니다. 그런데 실제로 테마 토글 버튼을 구현해 보니, classList를 바꾸는 것보다 setProperty로 변수 값을 직접 교체하는 쪽이 코드가 훨씬 간결하게 떨어졌습니다. 솔직히 이건 예상 밖이었습니다.
다크 모드 구현과 유지보수, 변수가 없었다면
CSS 변수의 진가를 처음 실감한 건 다크 모드 구현 작업에서였습니다. 미디어쿼리 안에서 변수 값만 재선언했더니 열다섯 줄로 전체 색상 전환이 끝났습니다. HTML 구조나 레이아웃 코드를 단 한 줄도 건드리지 않은 채로 말이죠. 여기서 prefers-color-scheme이란 사용자의 운영체제나 브라우저가 설정한 색상 모드 선호를 CSS가 감지하는 미디어쿼리입니다.
변수가 없었다면 어떻게 됐을까요. 기존 방식대로라면 CSS 전체를 복사해서 다크 모드용 색상 코드를 하나씩 덮어쓰는 작업을 했을 겁니다. 색상 값이 30개라면 30번, 50개라면 50번 수작업으로 찾아서 바꿔야 하고, 그 과정에서 하나라도 빠뜨리면 저처럼 배포 후에 버그가 발견됩니다. 이 경험을 하기 전까지는 "굳이 변수로 묶어야 하나"라는 생각이 있었는데, 한 번 겪고 나면 그 질문이 완전히 사라집니다.
처음 CSS 변수를 도입할 때 기존 코드를 변수로 전환하는 과정이 솔직히 귀찮았습니다. :root에 변수를 하나씩 선언하고, 기존 하드코딩된 색상 코드를 var()로 교체하는 작업은 시간이 걸립니다. 그런데 일단 세팅을 마치고 나서, :root에서 값 하나를 고쳤더니 헤더, 버튼, 링크, 배경이 전부 한꺼번에 바뀌는 걸 봤을 때 "이 한 번의 고생이 값을 한다"는 게 피부로 느껴졌습니다.
변수 이름 규칙을 초반에 정해두지 않으면 나중에 낭패를 봅니다. --main-color, --primary, --blue처럼 일관성 없이 섞어서 쓴 적이 있었는데, 변수가 열 개만 넘어가도 어떤 변수가 어디에 쓰이는지 파악하기가 어려워졌습니다. 특히 팀 프로젝트라면 변수 이름 규칙과 주석은 선택이 아닌 필수입니다. --color-, --font-, --spacing- 처럼 접두사(prefix) 규칙을 처음부터 정해두는 것을 권합니다. 이게 번거롭게 느껴질 수 있는데, 혼자 하는 프로젝트라도 3개월 뒤 본인이 다시 열었을 때 얼마나 다른지 체감하게 됩니다.
IE(Internet Explorer) 미지원 문제는 이제 대부분의 프로젝트에서 무시해도 되는 시대가 됐습니다. CSS 커스텀 프로퍼티는 현재 크롬, 파이어폭스, 사파리, 엣지 등 주요 브라우저에서 모두 지원됩니다. 다만 일부 기업 내부 시스템이나 공공기관 프로젝트에서는 여전히 IE 호환을 요구하는 경우가 있어서, 착수 전에 요건을 한 번 확인해두는 편이 안전합니다. 이 부분을 빠뜨렸다가 뒤늦게 호환 문제로 전체 구조를 다시 짠 경험이 있는 분이라면 무슨 말인지 바로 이해하실 겁니다.
지금 저는 새 블로그 스킨 작업을 시작할 때 HTML 구조보다 :root 변수 세트를 먼저 설계하는 게 습관이 됐습니다. 디자인 시스템의 토대를 먼저 깔고 시작하는 것과 마찬가지입니다. 한 번만 제대로 세팅해두면 유지보수 시간이 확연히 줄어드는 걸 직접 겪어봤습니다. 기존 CSS 파일이 있다면 먼저 색상 코드부터 변수로 묶어보는 것을 권합니다. 작은 것 하나 바꾸는 데서 체감이 시작됩니다.
참고:
MDN Web Docs — CSS Custom Properties
CSS-Tricks — A Complete Guide to Custom Properties
web.dev — prefers-color-scheme