한 줄 요약 — CSS 변수는 색·간격 같은 값에 이름표를 붙여 한곳에 모아 두는 도구다. --이름으로 통에 페인트를 담고 var(--이름)으로 꺼내 칠한다. 표준 값은 :root에 두고, 한 번에 바꾸고 싶을 땐 통 내용물만 갈아 끼우면 그 통으로 칠한 곳이 전부 바뀐다 — 다크 모드 전환이 한 줄로 끝나는 이유다.

학습 목표#

  • --이름으로 변수를 선언하고 var()로 꺼내 쓸 수 있다.
  • 전역 값은 왜 :root에 두는지 설명할 수 있다.
  • var(--이름, 기본값)으로 fallback을 줘 미지원 환경에서도 안 깨지게 할 수 있다.
  • 색 5개를 변수로 묶어 다크 모드를 한 줄로 전환할 수 있다.

오늘의 비유 — 한 번에 색을 바꿀 수 있는 페인트 통 라벨#

벽을 칠한다고 하자. 칠하는 사람마다 "이번엔 #2563eb 파랑"이라고 일일이 정확한 색 번호를 불러 주면, 나중에 그 파랑을 다른 톤으로 바꾸고 싶을 때 칠한 벽을 전부 찾아다니며 다시 색 번호를 고쳐야 한다. 벽이 수십 개면 한 군데를 빠뜨리기 십상이다.

대신 페인트 통에 라벨을 붙여 두면 어떨까. "강조색"이라고 적힌 통을 작업장 중앙 선반에 두고, 모두에게 "강조색 통에서 꺼내 칠하라"고만 한다. 나중에 톤을 바꿀 땐 그 통의 내용물만 갈아 끼우면, 강조색 통으로 칠한 벽이 한꺼번에 새 색으로 바뀐다. CSS 변수가 바로 이 라벨 붙은 통이다.

핵심 개념#

변수 만들고 꺼내 쓰기 — --이름var()#

변수 이름은 반드시 --로 시작한다. 값을 담는 건 통에 페인트를 붓는 일이고, var()는 그 통에서 꺼내 칠하는 일이다.

.card {
  --accent: #2563eb;          /* 통에 라벨(--accent)을 붙이고 색을 담는다 */
  border: 2px solid var(--accent);   /* 라벨로 꺼내 칠한다 */
  color: var(--accent);
}

--accent를 한 번만 바꾸면 그 통을 쓴 bordercolor가 같이 바뀐다. 같은 색을 두 번 적지 않으니 고칠 곳도 한 군데로 준다.

:root — 작업장 중앙 선반에 통을 둔다#

위 예시처럼 .card 안에 둔 통은 그 카드 안에서만 꺼낼 수 있다. 페이지 어디서든 쓰는 표준 색·간격은 가장 바깥, 곧 모든 요소의 조상인 :root에 둔다. :root는 문서의 최상위(<html>)를 가리키는, 중앙 선반 같은 자리다. 변수는 자식에게 상속되므로 여기 둔 통은 모든 방에서 꺼내 쓸 수 있다.

:root {
  --accent: #2563eb;
  --gap: 16px;
}

.btn { color: var(--accent); }
.list { gap: var(--gap); }

fallback — 통이 비었을 때 쓸 기본 페인트#

var()는 두 번째 값으로 기본값을 받는다. 라벨이 그 이름인 통을 못 찾으면 이 기본 페인트로 칠한다. 변수를 못 읽는 옛 브라우저나, 깜빡하고 선언을 빠뜨린 경우의 안전장치다.

.badge {
  background: var(--accent, #2563eb);   /* --accent가 없으면 이 파랑으로 칠한다 */
}

함께 따라하기 — 색 5개로 다크 모드를 한 줄로 전환#

표준 색 5개를 통에 담아 두고, 밤 버전 페인트를 따로 준비해 라벨만 갈아 끼워 본다.

<body data-theme="light">
  <header class="bar">한 줄 팔레트</header>
  <main class="panel">
    <p>이 영역의 색은 모두 통에서 꺼내 칠했다.</p>
    <a class="btn" href="#">강조 버튼</a>
  </main>
</body>
:root {
  --bg: #ffffff;
  --surface: #f4f4f5;
  --text: #1c1c1e;
  --muted: #6b7280;
  --accent: #2563eb;
}

[data-theme="dark"] {
  --bg: #18181b;
  --surface: #27272a;
  --text: #f4f4f5;
  --accent: #60a5fa;
}

body { background: var(--bg); color: var(--text); }
.panel { background: var(--surface); }
.btn { color: var(--accent); }

<body>data-themelight에서 dark로 한 글자만 바꾸면, 같은 라벨의 통 내용물이 밤 버전으로 갈리면서 배경이 어두워지고 글자가 밝아지며 강조 버튼도 톤이 바뀐다. 칠하는 규칙(var(--bg) 등)은 그대로 두고 통 안의 색만 바꾼 것이다. 시스템 설정을 그대로 따르고 싶다면 [data-theme="dark"] 대신 @media (prefers-color-scheme: dark) 블록에 같은 통들을 넣으면 된다.

흔한 실수 3가지#

1. 전역 토큰을 :root가 아닌 body에 정의해 위계가 꼬인다#

body에 전역 통을 두면 <body> 바깥 요소(예: 대화상자의 ::backdrop)에서는 그 통을 못 꺼낸다. 또 "표준은 위, 예외는 아래에서 덮어쓰기"라는 위계가 흐려진다.

/* (전역 통을 한 방 안(body)에 둬, body 바깥에선 꺼낼 수 없습니다) */
body {
  --accent: #2563eb;
}

전역 기본값은 가장 위 선반인 :root에 두고, 특정 구역만 다르게 칠하고 싶을 때 그 구역에서 같은 라벨을 덮어쓴다. 그러면 "기본은 중앙 선반, 예외는 그 방 안"으로 규칙이 분명해진다.

:root {
  --accent: #2563eb;          /* 표준 통은 중앙 선반에 */
}
.promo {
  --accent: #db2777;          /* 이 구역만 다른 색으로 덮어쓴다 */
}

2. fallback을 안 줘서 미지원 환경에서 깨진다#

선언을 빠뜨렸거나 변수를 못 읽는 환경에서 var(--x)가 빈 값이 되면, 그 속성은 통째로 무효가 돼 색이 사라질 수 있다.

/* (--accent를 어디서도 선언하지 않으면 글자색이 정해지지 않습니다) */
.btn {
  color: var(--accent);
}

자주 쓰는 핵심 값엔 두 번째 인자로 기본 페인트를 함께 적어 둔다. 통이 비어 있어도 이 색으로 칠해진다.

.btn {
  color: var(--accent, #2563eb);
}

3. SASS 변수와 CSS 변수의 동작 차이를 모른다#

SASS(Syntactically Awesome Style Sheets, CSS를 더 편하게 쓰게 해 주는 전처리기)의 $color 변수는 빌드 시점에 실제 값으로 바뀌어 사라진다 — 완성된 CSS에는 변수가 남지 않는다. 반면 CSS 변수(--color)는 브라우저가 페이지를 그리는 실행 중에도 살아 있다. 그래서 미디어 쿼리나 data-theme 전환처럼 런타임에 통 내용물을 갈아 끼우는 일은 CSS 변수여야 가능하다. 다크 모드 토글을 SASS 변수로 만들려다 "왜 안 바뀌지" 하는 실수가 여기서 나온다. 위 실습처럼 런타임에 바뀌어야 하는 값은 --로 시작하는 CSS 변수로 둔다.

오늘 배운 것 체크리스트#

  • --이름으로 변수를 선언하고 var(--이름)으로 꺼내 쓴다.
  • 페이지 전역에서 쓰는 값은 :root에 둔다.
  • 핵심 값엔 var(--이름, 기본값)으로 fallback을 준다.
  • 색 묶음을 변수로 두면 다크 모드를 통 내용물 교체 한 번으로 전환할 수 있다.
  • SASS 변수는 빌드 때 사라지고, CSS 변수는 런타임에 살아 있다.

자주 묻는 질문#

Q. CSS 변수는 어디에 선언해야 하나요?

A. 페이지 전체에서 쓰는 색·간격 같은 표준 값은 :root에 두는 게 기본입니다. :root는 모든 요소의 최상위 조상이라, 여기 선언한 변수가 상속을 타고 어디서든 닿습니다. 특정 컴포넌트 안에서만 쓰는 값이라면 그 요소 안에 선언해 범위를 좁히면 됩니다.

Q. 같은 변수로 다크 모드 색을 바꾸려면요?

A. :root에 라이트용 색 통을 두고, [data-theme="dark"](또는 @media (prefers-color-scheme: dark))에서 같은 이름의 변수를 어두운 색으로 다시 선언합니다. 칠하는 규칙은 그대로 두고 통 내용물만 갈리므로, data-theme 한 글자만 바꿔도 변수를 쓴 모든 곳이 한꺼번에 바뀝니다.

Q. JS로 CSS 변수를 바꿀 수도 있나요?

A. 가능합니다. CSS 변수는 런타임에 살아 있어서, 자바스크립트로 특정 요소의 변수 값을 새로 지정하면 그 통으로 칠한 곳이 즉시 바뀝니다. 사용자가 직접 색을 고르는 테마 같은 기능이 여기서 필요해집니다. 다만 구체적인 코드는 이 시리즈 범위 밖이라, 뒤따르는 자바스크립트 편에서 다룹니다.

다음 시간 예고#

내일은 스크롤 인터랙션 — sticky 헤더와 scroll-snap을 다룬다. 오늘 값에 이름을 붙여 한곳에서 관리했다면, 다음 시간엔 스크롤을 한 화면씩 딱딱 멈추게 하는 효과를 자바스크립트 없이 CSS만으로 만든다.

더 알아보기#