한 줄 정답 —
--이름: 값;으로 선언하고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)**이다. color나 margin 같은 정해진 속성이 아니라, --로 시작하는 이름을 내가 만들어 값을 담아 두는 그릇이다. 색·간격·폰트 크기처럼 여러 곳에서 같은 값을 반복하는 것을 한 군데서 관리할 때 쓴다.
문법은 두 부분이다. 값을 넣는 선언과, 그 값을 꺼내는 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 한 줄만 고치면 그 변수를 쓰는 모든 곳이 한꺼번에 바뀐다. 이게 "디자인 토큰"을 변수로 관리하는 핵심 이점이다.
:root는 html 선택자와 같은 요소를 가리키지만 명시도가 한 단계 더 높다. 전역 변수는 관례적으로 :root에 둔다. 단위를 더 깊이 보고 싶다면 CSS 단위 차이 — px·em·rem·vw·vh, 언제 무엇을 쓸까에서 변수에 담을 길이 값의 기준을 함께 잡아 두면 좋다.
변수는 상속된다 — 범위는 선언한 요소의 자손#
:root가 아니라 특정 요소에 선언하면, 그 변수는 그 요소와 자손에서만 유효하다. 커스텀 속성이 일반 속성처럼 상속되기 때문이다.
.card { --accent: #16a34a; } /* .card 와 그 안에서만 유효 */
.card .title { color: var(--accent); } /* 초록 적용 */
.footer .title { color: var(--accent); } /* .card 밖이라 미정의 → 무효 */
같은 변수 이름을 안쪽 요소에서 다시 선언하면 그 부분만 값이 덮어써진다(캐스케이드). 이 성질을 이용해 컴포넌트 단위로 색 테마를 국소적으로 바꿀 수 있다.
다크모드 — 변수 값만 토글하기#
CSS 변수가 빛을 보는 대표 사례가 다크모드다. 색을 변수로 묶어 두면, 테마를 바꿀 때 각 요소를 일일이 고칠 필요 없이 변수 값만 교체하면 된다.
단계별로 따라하기#
- 색을 직접 쓰지 말고 모두 변수로 선언한다(
:root에 라이트 테마 값). - 다크 테마용으로 같은 변수들을 다른 값으로 다시 선언한다 —
prefers-color-scheme: dark(기기 설정 추종) 또는[data-theme="dark"]같은 클래스(수동 토글) 안에. - 본문에서는 변수만 참조한다. 테마가 바뀌면 변수 값이 갈리며 색이 한 번에 전환된다.
: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 변수는 디자인 토큰·다크모드·반응형의 토대다. 강의 흐름에서 더 깊이 보거나, 변수에 담을 단위·미디어쿼리로 이어 가면 좋다.
- CSS 변수 — var()와 --custom으로 디자인 토큰 만들기 — 같은 주제를 60일 강의 흐름에서 디자인 토큰 관점으로 더 깊이
- CSS 단위 차이 — px·em·rem·vw·vh, 언제 무엇을 쓸까 — 변수에 담을 길이 값의 기준을 잡는 단위 정리
- CSS 미디어쿼리 사용법 — 모바일 퍼스트 반응형 한 번에 정리 — 다크모드
@media와 반응형 분기를 함께 보는 글