한 줄 정답--이름: 값;으로 선언하고 var(--이름)으로 꺼내 쓴다. 보통 문서 전체에서 쓰려고 :root { --brand: #2563eb; }처럼 최상위에 모아 두고, color: var(--brand);로 참조한다. 값이 없을 때를 대비하려면 var(--brand, #333)처럼 폴백을 두 번째 인자로 준다. CSS 변수는 일반 속성처럼 상속되고 캐스케이드를 탄다 — 그래서 부모에서 값만 바꾸면 자식 전체가 따라 바뀌고, 다크모드도 :root의 변수 값만 교체하면 끝난다.

핵심 요약#

  • 선언은 --이름: 값;, 사용은 var(--이름). 이름은 두 하이픈으로 시작하고 대소문자를 구분한다(--Color--color).
  • :root에 두면 전역 변수가 된다. :root는 문서의 최상위 요소(<html>)라 거기 선언한 변수가 모든 요소에 상속된다.
  • 폴백var(--이름, 기본값)의 둘째 인자. 변수가 정의돼 있지 않을 때만 기본값이 쓰인다.
  • 변수는 토큰을 담는다, 계산값이 아니다. --gap: 20;margin: var(--gap)px;는 동작하지 않는다 — 단위를 붙이려면 calc(var(--gap) * 1px).
  • 다크모드:root(또는 특정 클래스·prefers-color-scheme)에서 변수 값만 바꾸면 그 변수를 쓰는 모든 색이 한 번에 전환된다.

CSS 변수 사용법의 기본 — 선언과 var()#

CSS 변수의 정식 이름은 **커스텀 속성(custom properties)**이다. colormargin 같은 정해진 속성이 아니라, --로 시작하는 이름을 내가 만들어 값을 담아 두는 그릇이다. 색·간격·폰트 크기처럼 여러 곳에서 같은 값을 반복하는 것을 한 군데서 관리할 때 쓴다.

문법은 두 부분이다. 값을 넣는 선언과, 그 값을 꺼내는 var() 함수.

/* 선언: -- 로 시작하는 이름에 값을 담는다 */
.card {
  --card-padding: 16px;
  --card-color: #1f2937;
}

/* 사용: var()로 꺼내 쓴다 */
.card {
  padding: var(--card-padding);
  color: var(--card-color);
}

이름은 두 하이픈(--)으로 시작하기만 하면 자유롭게 지을 수 있다. 다만 대소문자를 구분한다 — --card-color--Card-Color는 서로 다른 변수다. 값에는 색, 길이, 문자열은 물론 여러 토큰을 묶은 덩어리(1px solid #ccc 같은)도 담을 수 있다.

폴백 — var(--이름, 기본값)#

var()의 둘째 인자는 폴백이다. 첫째 인자의 변수가 정의돼 있지 않을 때 대신 쓸 값이다.

.btn {
  /* --btn-bg 가 어디에도 선언돼 있지 않으면 #e5e7eb 사용 */
  background: var(--btn-bg, #e5e7eb);
}

컴포넌트를 만들 때 유용하다. 바깥에서 --btn-bg를 지정하면 그 색을, 안 주면 기본 색을 쓰도록 안전망을 두는 식이다. 폴백 안에 또 var()를 중첩할 수도 있다: var(--a, var(--b, gray)).

루트 변수 root — :root에 전역으로 모으기#

대부분의 변수는 문서 전체에서 공통으로 쓰고 싶다. 그럴 때 :root에 선언한다. :root는 문서의 최상위 요소를 가리키는 가상 클래스로, HTML에서는 <html>과 같다. 여기 선언한 변수는 상속을 타고 모든 요소로 내려간다.

:root {
  --brand: #2563eb;
  --text: #1f2937;
  --space: 8px;
  --radius: 6px;
}

.button {
  background: var(--brand);
  border-radius: var(--radius);
  padding: var(--space) calc(var(--space) * 2);
}

a { color: var(--brand); }

이렇게 모아 두면 브랜드 색을 바꿀 때 :root--brand 한 줄만 고치면 그 변수를 쓰는 모든 곳이 한꺼번에 바뀐다. 이게 "디자인 토큰"을 변수로 관리하는 핵심 이점이다.

:roothtml 선택자와 같은 요소를 가리키지만 명시도가 한 단계 더 높다. 전역 변수는 관례적으로 :root에 둔다. 단위를 더 깊이 보고 싶다면 CSS 단위 차이 — px·em·rem·vw·vh, 언제 무엇을 쓸까에서 변수에 담을 길이 값의 기준을 함께 잡아 두면 좋다.

변수는 상속된다 — 범위는 선언한 요소의 자손#

:root가 아니라 특정 요소에 선언하면, 그 변수는 그 요소와 자손에서만 유효하다. 커스텀 속성이 일반 속성처럼 상속되기 때문이다.

.card { --accent: #16a34a; }   /* .card 와 그 안에서만 유효 */

.card .title { color: var(--accent); }  /* 초록 적용 */
.footer .title { color: var(--accent); } /* .card 밖이라 미정의 → 무효 */

같은 변수 이름을 안쪽 요소에서 다시 선언하면 그 부분만 값이 덮어써진다(캐스케이드). 이 성질을 이용해 컴포넌트 단위로 색 테마를 국소적으로 바꿀 수 있다.

다크모드 — 변수 값만 토글하기#

CSS 변수가 빛을 보는 대표 사례가 다크모드다. 색을 변수로 묶어 두면, 테마를 바꿀 때 각 요소를 일일이 고칠 필요 없이 변수 값만 교체하면 된다.

단계별로 따라하기#

  1. 색을 직접 쓰지 말고 모두 변수로 선언한다(:root에 라이트 테마 값).
  2. 다크 테마용으로 같은 변수들을 다른 값으로 다시 선언한다 — prefers-color-scheme: dark(기기 설정 추종) 또는 [data-theme="dark"] 같은 클래스(수동 토글) 안에.
  3. 본문에서는 변수만 참조한다. 테마가 바뀌면 변수 값이 갈리며 색이 한 번에 전환된다.
:root {
  --bg: #ffffff;
  --fg: #1f2937;
  --brand: #2563eb;
}

/* 기기가 다크모드일 때 같은 변수에 다른 값 */
@media (prefers-color-scheme: dark) {
  :root {
    --bg: #111827;
    --fg: #e5e7eb;
    --brand: #60a5fa;
  }
}

/* 또는 클래스로 수동 토글 (JS로 html에 data-theme 부여) */
[data-theme="dark"] {
  --bg: #111827;
  --fg: #e5e7eb;
  --brand: #60a5fa;
}

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

prefers-color-scheme는 사용자의 기기 설정을 따라가고, data-theme 같은 클래스 방식은 화면의 버튼으로 직접 전환하게 해 준다. 둘을 섞어 "기본은 기기 설정, 버튼으로 덮어쓰기"로 만드는 것도 흔하다. 미디어쿼리 자체가 처음이라면 CSS 미디어쿼리 사용법 — 모바일 퍼스트 반응형 한 번에 정리를 먼저 보면 위 @media 줄이 더 또렷해진다.

CSS 변수가 안 먹을 때 — 흔한 원인#

변수가 적용되지 않는다면 대개 다음 중 하나다.

  • 변수가 그 요소까지 상속되지 않음. :root가 아니라 다른 요소에 선언했고, 지금 쓰려는 요소가 그 자손이 아니면 변수는 미정의다. 전역으로 쓸 거면 :root에 둔다.
  • 이름 오타·대소문자 불일치. --Main으로 선언하고 var(--main)으로 쓰면 다른 변수다. 미정의 변수는 폴백이 없으면 그 속성을 무효로 만든다.
  • 단위를 문자열로 붙이려 함. --w: 50;width: var(--w)%;는 안 된다. 변수는 값을 그대로 치환할 뿐이라 50%라는 토큰이 만들어지지 않는다. width: calc(var(--w) * 1%)처럼 calc()로 단위를 곱하거나, 애초에 --w: 50%;로 단위까지 담는다.
  • 선택자나 속성 이름 자리에 쓰려 함. var()속성의 값 자리에서만 동작한다. 선택자나 미디어쿼리 조건(@media (min-width: var(--bp)))에는 쓸 수 없다.
  • 치환 결과가 잘못된 값. var()로 바뀐 최종 값이 그 속성에 유효하지 않으면, 그 속성은 상속값 또는 초깃값으로 처리된다. 같은 변수를 여러 속성에서 재사용할 때 한쪽에서만 깨지는 경우가 여기 해당한다.

자주 묻는 질문#

Q. CSS 변수는 어떻게 선언하고 사용하나요?

A. 선언은 --이름: 값;, 사용은 var(--이름)입니다. 예를 들어 :root { --brand: #2563eb; }로 선언하고 color: var(--brand);로 꺼내 씁니다. 이름은 두 하이픈으로 시작하며 대소문자를 구분합니다. 값이 없을 때를 대비하려면 var(--brand, #333)처럼 둘째 인자에 폴백을 둡니다.

Q. 루트 변수(:root)는 무엇이고 왜 거기에 선언하나요?

A. :root는 문서의 최상위 요소(<html>)를 가리키는 선택자입니다. 커스텀 속성은 상속되기 때문에 :root에 선언하면 그 변수가 모든 요소로 내려가 전역 변수처럼 쓸 수 있습니다. 브랜드 색·간격 같은 공통 토큰을 :root 한곳에 모아 두면, 한 줄만 바꿔 사이트 전체에 반영할 수 있습니다.

Q. CSS 변수로 다크모드는 어떻게 만드나요?

A. 색을 모두 변수로 선언한 뒤, 다크 테마에서 같은 변수에 다른 값을 다시 선언하면 됩니다. @media (prefers-color-scheme: dark)로 기기 설정을 따르거나, [data-theme="dark"] 같은 클래스로 수동 전환합니다. 본문은 변수만 참조하므로, 변수 값이 갈리는 순간 그 변수를 쓰는 모든 색이 한 번에 바뀝니다.

Q. CSS 변수가 안 먹을 때는 무엇을 확인하나요?

A. 가장 흔한 원인은 상속 범위입니다. :root가 아닌 요소에 선언했고 지금 요소가 그 자손이 아니면 변수는 미정의입니다. 그 외에 이름 대소문자 불일치, var(--w)px처럼 단위를 문자열로 붙인 경우(→ calc() 사용), 선택자나 미디어쿼리 조건에 var()를 쓴 경우가 있습니다. var()는 속성 값 자리에서만 동작합니다.

관련 글#

CSS 변수는 디자인 토큰·다크모드·반응형의 토대다. 강의 흐름에서 더 깊이 보거나, 변수에 담을 단위·미디어쿼리로 이어 가면 좋다.

더 알아보기#