Custom properties for cascading variables/Using custom properties

김동현·2026년 3월 18일

mdn 학습 번역 - CSS

목록 보기
55/190

안녕하세요! 포트폴리오를 준비하시느라, 그리고 다가올 기술 및 임원 면접을 대비하시느라 정말 바쁜 시간을 보내고 계실 텐데요. 오늘 함께 살펴볼 문서는 모던 웹 개발에서 절대 빠질 수 없는 핵심 기술 중 하나입니다.

바로 'CSS 사용자 지정 속성(변수) 사용하기'입니다. Figma에서 전달받은 디자인 토큰(색상, 폰트 크기 등)을 코드로 깔끔하게 옮기고, 다크 모드를 구현하거나 Storybook 같은 컴포넌트 주도 개발(CDD) 환경에서 동적으로 테마를 스위칭할 때 가장 강력한 무기가 되는 녀석이죠.

그럼 MDN 공식 문서의 내용을 하나도 빠짐없이, 이해하기 쉬운 구어체로 차근차근 풀어서 설명해 드릴게요. 면접에서도 자신 있게 답변하실 수 있도록 실무 팁도 듬뿍 담았습니다!


CSS 사용자 지정 속성(변수) 사용하기 (Using CSS custom properties (variables))

사용자 지정 속성(Custom properties) (종종 CSS 변수(CSS variables) 또는 종속 변수(cascading variables)라고도 불림)은 CSS 작성자가 직접 정의하는 요소로, 문서 전체에서 재사용할 특정 값을 나타냅니다. 이 속성들은 @property at-규칙(at-rule)을 사용하거나, 사용자 지정 속성 구문(custom property syntax)을 사용하여 설정합니다 (예: --primary-color: blue;). 이렇게 만들어진 사용자 지정 속성에는 CSS var() 함수를 통해 접근할 수 있습니다 (예: color: var(--primary-color);).

복잡한 웹사이트는 CSS의 양이 엄청나게 많고, 이로 인해 똑같은 CSS 값이 여러 번 반복해서 사용되는 경우가 흔합니다. 예를 들어, 스타일시트 안의 수백 군데에서 동일한 색상이 사용되는 것을 쉽게 볼 수 있죠. 이렇게 여기저기 중복된 색상을 하나하나 바꾸려면 모든 규칙과 CSS 파일을 뒤져서 '찾기 및 바꾸기(Search and Replace)'를 해야만 합니다. 사용자 지정 속성을 사용하면 어떤 값을 한 곳에서만 정의해 두고, 다른 여러 곳에서 그 값을 참조(reference)할 수 있기 때문에 작업이 훨씬 수월해집니다.

또 다른 장점은 바로 '가독성(readability)'과 '의미(semantics)'입니다. 예를 들어, 16진수 색상 코드인 #00ff00을 직접 보는 것보다는 --main-text-color라는 변수명을 보는 것이 훨씬 이해하기 쉽죠. 특히 그 색상이 다양한 상황(context)에서 쓰일 때는 더욱 그렇습니다.

두 개의 대시(--)를 사용하여 정의된 사용자 지정 속성은 CSS의 캐스케이드(cascade, 종속) 규칙을 따르며, 부모 요소로부터 값을 상속(inherit)받습니다.
반면, @property at-규칙을 사용하면 사용자 지정 속성을 좀 더 세밀하게 제어할 수 있습니다. 부모로부터 값을 상속받을지 여부, 초깃값(initial value)이 무엇인지, 그리고 어떤 타입(type constraint)을 가져야 하는지까지 지정할 수 있죠.

참고:
변수는 미디어 쿼리(media queries)와 컨테이너 쿼리(container queries) '자체'에서는 작동하지 않습니다. 여러분은 요소의 어떤 속성에든 값의 일부로 var() 함수를 사용할 수 있습니다. 하지만 속성 이름(property names)이나 선택자(selectors), 또는 속성값이 아닌 다른 곳에는 var()를 사용할 수 없습니다. 즉, 미디어 쿼리나 컨테이너 쿼리의 조건문 안에서는 사용할 수 없다는 뜻입니다.

💡 강사 팁:
이 부분은 면접에서도 자주 묻는 함정입니다! @media (min-width: var(--breakpoint)) 처럼 미디어 쿼리의 조건식 안에는 CSS 변수를 쓸 수 없습니다. 미디어 쿼리가 계산되는 시점에는 아직 어떤 요소에 변수가 적용되었는지 DOM 트리를 알 수 없기 때문이죠. 단, 미디어 쿼리 블록 '안쪽의' CSS 속성값으로는 자유롭게 쓸 수 있습니다!


이 문서의 내용 (In this article)


사용자 지정 속성 선언하기 (Declaring custom properties)

CSS에서 사용자 지정 속성을 선언할 때는 속성 이름 앞에 접두사로 두 개의 대시(--)를 붙이거나, @property at-규칙을 사용할 수 있습니다. 다음 섹션에서는 이 두 가지 방법을 사용하는 방법에 대해 설명합니다.

두 개의 대시(--) 접두사 사용하기

두 개의 대시가 붙은 사용자 지정 속성은 --로 시작하며, 그 뒤에 속성 이름(예: --my-property)이 오고, 속성값으로는 유효한 CSS 값이라면 무엇이든 올 수 있습니다.
다른 일반적인 CSS 속성과 마찬가지로, 이 역시 룰셋(ruleset) 내부에 작성됩니다.
아래 예제는 --main-bg-color라는 사용자 지정 속성을 만들고, <named-color> 값 중 하나인 brown을 지정하는 방법을 보여줍니다.

section {
  --main-bg-color: brown;
}

룰셋에 주어진 선택자(위 예제의 경우 <section> 요소)는 해당 사용자 지정 속성이 사용될 수 있는 스코프(scope, 유효 범위)를 정의합니다.
이러한 이유로, 전역(globally)으로 참조할 수 있도록 사용자 지정 속성을 :root 가상 클래스(pseudo-class)에 정의하는 것이 아주 흔한 관행(common practice)입니다.

:root {
  --main-bg-color: brown;
}

하지만 항상 이렇게 해야만 하는 것은 아닙니다. 사용자 지정 속성의 스코프를 좁게 제한해야 하는 합당한 이유가 있을 수도 있으니까요.

참고:
사용자 지정 속성의 이름은 대소문자를 구분(case sensitive)합니다. 즉, --my-color--My-color는 서로 완전히 다른 변수로 취급됩니다.

@property at-규칙 사용하기

@property at-규칙을 사용하면 사용자 지정 속성을 훨씬 더 표현력 있게 정의할 수 있습니다. 속성에 타입을 연결하거나, 기본값을 설정하거나, 상속 여부를 제어할 수 있는 능력이 생기죠.
다음 예제는 <color> 값을 기대하는 --logo-color라는 사용자 지정 속성을 만듭니다.

@property --logo-color {
  syntax: "<color>";
  inherits: false;
  initial-value: #c0ffee;
}

만약 CSS 내부가 아니라 JavaScript에서 직접 사용자 지정 속성을 정의하거나 다루고 싶다면, 이를 위한 전용 API가 준비되어 있습니다. 어떻게 작동하는지는 CSS 속성 및 값 API (CSS Properties and Values API) 페이지에서 읽어보실 수 있습니다.

💡 강사 팁:
최신 CSS 스펙인 @property는 정말 유용합니다. 기존의 -- 변수 방식은 브라우저가 이 변수가 색상인지, 숫자인지, 각도인지 알지 못해서 애니메이션(Transition)을 매끄럽게 주지 못하는 한계가 있었습니다. 하지만 @property로 타입을 명시해주면 그라데이션 색상이 부드럽게 변하는 애니메이션 등 강력한 효과를 낼 수 있습니다.

var()로 사용자 지정 속성 참조하기

사용자 지정 속성을 어떤 방식으로 정의했든 상관없이, 이 속성을 사용할 때는 표준 속성값이 들어갈 자리에 var() 함수를 사용하여 참조하면 됩니다.

details {
  background-color: var(--main-bg-color);
}

사용자 지정 속성 시작하기 (First steps with custom properties)

스타일을 적용해 볼 HTML을 살펴보면서 시작해 봅시다. 컨테이너 역할을 하는 <div>가 있고, 그 안에는 여러 자식 요소들이 들어 있으며 일부 요소는 중첩(nested)되어 있습니다.

<div class="container">
  <div class="one">
    <p>One</p>
  </div>
  <div class="two">
    <p>Two</p>
    <div class="three">
      <p>Three</p>
    </div>
  </div>
  <input class="four" placeholder="Four" />
  <textarea class="five">Five</textarea>
</div>

우리는 클래스를 기반으로 몇 가지 다른 요소들을 스타일링하기 위해 아래의 CSS를 사용할 것입니다. (색상에만 집중하기 위해 일부 레이아웃 관련 규칙은 생략했습니다.)
클래스에 따라 요소들에 teal(청록색) 또는 pink(분홍색) 배경색을 주고 있습니다.

/* 클래스마다 각각 색상을 지정합니다 */
.one {
  background-color: teal;
}

.two {
  color: black;
  background-color: pink;
}

.three {
  color: white;
  background-color: teal;
}

.four {
  background-color: teal;
}

.five {
  background-color: teal;
}

이 규칙들을 보면 동일한 색상 값이 여기저기 반복되고 있는 것을 알 수 있습니다. 이럴 때야말로 사용자 지정 속성을 사용해서 반복되는 값을 대체할 절호의 기회죠!
.container 스코프 안에 --main-bg-color를 정의해 두고 여러 곳에서 참조하도록 스타일을 업데이트하면 다음과 같은 모습이 됩니다.

/* 여기서 --main-bg-color를 정의합니다 */
.container {
  --main-bg-color: teal;
}

/* 각 클래스에서 변수를 이용해 색상을 세팅합니다 */
.one {
  background-color: var(--main-bg-color);
}

.two {
  color: black;
  background-color: pink;
}

.three {
  color: white;
  background-color: var(--main-bg-color);
}

.four {
  background-color: var(--main-bg-color);
}

.five {
  background-color: var(--main-bg-color);
}

결과는 이전과 완벽히 동일합니다.


:root 가상 클래스 사용하기 (Using the :root pseudo-class)

어떤 CSS 선언들의 경우, 캐스케이드 상단(더 높은 조상 요소)에 선언해 두고 CSS 상속(inheritance)이 알아서 해결하도록 놔두는 것이 가능할 때도 있습니다. 하지만 꽤 복잡하고 큰 프로젝트에서는 이게 항상 가능하지만은 않습니다. 사용자 지정 속성을 :root 가상 클래스에 정의해 두고 문서 전체에서 필요할 때마다 가져다 쓰면, 불필요한 반복을 크게 줄일 수 있습니다.

/* 여기서 전역 변수를 정의합니다 */
:root {
  --main-bg-color: teal;
}

/* 여러 클래스에 한 번에 적용합니다 */
.one,
.three,
.four,
.five {
  background-color: var(--main-bg-color);
}

.two {
  color: black;
  background-color: pink;
}

이 방식 역시 이전 예제와 똑같은 결과를 냅니다. 하지만 우리가 원하는 속성값(--main-bg-color: teal;)을 '단 하나의 권위 있는 기준점(canonical declaration)'에 선언해 둘 수 있다는 장점이 생기죠. 나중에 프로젝트 전체에 걸쳐 이 색상을 변경해야 할 때 그야말로 진가를 발휘하게 됩니다.


사용자 지정 속성의 상속 (Inheritance of custom properties)

@property가 아닌 두 개의 대시 --를 사용해 정의된 사용자 지정 속성은 항상 부모의 값을 상속받습니다.
다음 예제를 통해 확인해 볼까요?

<div class="one">
  <p>One</p>
  <div class="two">
    <p>Two</p>
    <div class="three"><p>Three</p></div>
    <div class="four"><p>Four</p></div>
  </div>
</div>
div {
  background-color: var(--box-color);
}

.two {
  --box-color: teal;
}

.three {
  --box-color: pink;
}

상속에 따른 var(--box-color)의 결과는 다음과 같습니다:

  • class="one": 유효하지 않은 값(invalid value). (이런 방식으로 정의된 사용자 지정 속성의 기본 상태입니다. 배경색이 그려지지 않습니다.)
  • class="two": teal
  • class="three": pink
  • class="four": teal (부모인 .two로부터 상속받음)

위의 예제들이 보여주는 사용자 지정 속성의 한 가지 특징은, 이들이 다른 프로그래밍 언어(JavaScript 등)의 변수와 정확히 똑같이 행동하지는 않는다는 점입니다.
CSS 변수의 값은 딱 그 값이 필요한 요소(where it is needed)에서 계산될 뿐이지, 스타일시트 어딘가에 저장해 두고 아무 데서나 막 꺼내 쓸 수 있는 성질의 것이 아닙니다.
예를 들어, 어떤 요소에 변수를 설정해 놨다고 해서 그 요소의 '형제(sibling)' 요소의 자식에게 그 값을 적용시킬 수는 없습니다. 해당 변수는 오직 매칭되는 선택자와 그 '자손(descendants)'들에게만 유효하답니다.

@property를 사용하여 상속 제어하기

@property at-규칙을 사용하면 해당 속성이 상속될지 안 될지를 여러분이 직접 명시할 수 있습니다.
다음 예제는 @property를 사용해 사용자 지정 속성을 만드는 방법을 보여줍니다.
상속을 비활성화(inherits: false;) 했고, 데이터 타입을 <color>로 정의했으며, 초깃값으로 teal을 설정했습니다.

부모 요소(.parent)에서 --box-color 값을 green으로 설정하고, 배경색으로 지정했습니다.
자식 요소(.child) 역시 background-color: var(--box-color)를 사용하고 있습니다. 만약 상속이 켜져 있었다면(혹은 -- 구문으로만 정의되었다면) 우리는 당연히 자식 요소의 배경색도 green이 될 것이라고 예상했을 겁니다.

<div class="parent">
  <p>Parent element</p>
  <div class="child">
    <p>Child element with inheritance disabled for --box-color.</p>
  </div>
</div>
@property --box-color {
  syntax: "<color>";
  inherits: false;
  initial-value: teal;
}

.parent {
  --box-color: green;
  background-color: var(--box-color);
}

.child {
  width: 80%;
  height: 40%;
  background-color: var(--box-color);
}

하지만 결과는 다릅니다. at-규칙에서 inherits: false;로 설정되어 있고 .child 스코프 내부에는 --box-color 값이 따로 선언되지 않았기 때문에, 부모로부터 물려받았어야 할 green 대신 초깃값인 teal이 적용된 것을 확인할 수 있습니다.


사용자 지정 속성 폴백(대체) 값 (Custom property fallback values)

변수를 가져다 쓸 때 혹시라도 그 변수가 정의되지 않았을 경우를 대비해, var() 함수를 사용하거나 @property at-규칙의 initial-value를 통해 '폴백 값(fallback values)'을 정의해 둘 수 있습니다.

참고:
폴백 값은 구형 브라우저에서 'CSS 사용자 지정 속성 기능 자체가 지원되지 않을 때'를 대비한 호환성 해결책이 아닙니다. 기능 자체가 지원되지 않는 브라우저에서는 폴백 값도 아무런 소용이 없습니다.
폴백 값은 브라우저가 CSS 변수 기능을 완벽하게 지원하긴 하는데, 아직 해당 변수가 정의되지 않았거나 유효하지 않은 값이 들어왔을 때 대신 사용할 다른 값을 지정해 주는 용도입니다.

var() 함수에서 폴백 정의하기

var() 함수를 사용하면 해당 변수가 정의되지 않았을 때 대신 적용할 여러 개의 폴백 값(대체 값)을 정의할 수 있습니다. 이 기능은 웹 컴포넌트 (Custom Elements)Shadow DOM을 다룰 때 매우 유용합니다.

함수의 첫 번째 인자는 가져올 사용자 지정 속성의 이름입니다. 두 번째 인자는 선택적인 폴백 값으로, 참조된 사용자 지정 속성이 유효하지 않거나 없을 때 대체재로 사용됩니다.
이 함수는 인자를 구분할 때 첫 번째 쉼표(,)를 기준으로 잡고, 그 뒤에 오는 모든 것을 두 번째 인자(폴백)로 통째로 할당합니다. 만약 두 번째 인자마저 유효하지 않다면 폴백은 실패하게 됩니다. 예를 볼까요?

.one {
  /* --my-var가 정의되지 않았다면 'red' 적용 */
  color: var(--my-var, red);
}

.two {
  /* --my-var와 --my-background가 모두 정의되지 않았다면 'pink' 적용 */
  color: var(--my-var, var(--my-background, pink));
}

.three {
  /* 이렇게 쓰면 폴백 값 전체가 "--my-background, pink" 라는 알 수 없는 값이 되어 실패합니다 */
  color: var(--my-var, --my-background, pink);
}

위의 두 번째 예제(var(--my-var, var(--my-background, pink)))처럼, 폴백 자리에 또 다른 사용자 지정 속성을 포함시키는(중첩하는) 것이 var()와 함께 둘 이상의 폴백을 제공하는 올바른 방법입니다.
다만 이렇게 중첩된 변수를 계속 파고들어 구문 분석을 해야 하므로 약간의 성능(performance) 저하가 있을 수 있다는 점은 알고 계시는 것이 좋습니다.

참고:
사용자 지정 속성과 마찬가지로, 폴백 값의 구문 내에도 쉼표(,)를 허용합니다. 예를 들어, var(--foo, red, blue)red, blue라는 텍스트 전체를 폴백으로 정의한 것입니다. 첫 번째 쉼표부터 괄호가 끝날 때까지의 모든 내용이 통째로 하나의 폴백 값으로 간주됩니다.

@property 초깃값을 이용한 폴백

var()를 사용하는 것 외에도, @property at-규칙에 정의된 initial-value를 폴백 메커니즘으로 사용할 수 있습니다.
사실 이건 방금 전 @property를 사용하여 상속 제어하기 섹션에서 이미 확인한 내용이기도 하죠.

다음 예제에서는 @property at-규칙을 사용해 --box-color의 초깃값을 teal로 설정합니다.
이후 이어지는 룰셋에서 .two 안의 --box-colorpink로 설정하려고 했지만... 아뿔싸! 값을 peenk라고 오타를 내버렸네요.
세 번째 <div>에서는 유효한 <color>을 기대하는 변수에 2rem이라는 크기 단위를 줘버렸습니다.
2rempeenk 모두 색상으로서 유효하지 않은 값이므로, 이럴 땐 자동으로 안전장치인 초깃값 teal이 적용됩니다.

@property --box-color {
  syntax: "<color>";
  initial-value: teal;
  inherits: false;
}

.one {
  --box-color: pink;
  background-color: var(--box-color); /* pink 적용 */
}

.two {
  --box-color: peenk; /* 오타 발생! */
  background-color: var(--box-color); /* 초깃값 teal 적용 */
}

.three {
  --box-color: 2rem; /* 색상이 아닌 값! */
  background-color: var(--box-color); /* 초깃값 teal 적용 */
}

유효하지 않은 사용자 지정 속성 (Invalid custom properties)

각각의 일반 CSS 속성들은 정해진 유효한 값의 집합을 가집니다.
만약 유효하지 않은 값을 어떤 속성에 억지로 할당하려고 하면, 브라우저는 그 값을 무효(invalid) 처리해 버립니다.

브라우저가 일반적인 CSS 속성에서 유효하지 않은 값(예를 들어, color 속성에 16px을 넣는 경우)을 발견하면, 해당 선언 자체를 아예 버려버리고(discards) 그 선언이 애초에 없었던 것처럼 이전에 적용되었던 값을 그대로 유지합니다.
아래 예제는 일반 CSS 선언이 유효하지 않을 때 어떤 일이 일어나는지 보여줍니다. color: 16px;은 버려지고, 그 위에 선언되었던 color: blue 규칙이 대신 적용되는 모습이죠.

p {
  font-weight: bold;
  color: blue;
}

p {
  /* 앗, 색상이 아니잖아! (무시됨) */
  color: 16px;
}

하지만 사용자 지정 속성의 경우는 다릅니다. 이 변수들이 파싱(구문 분석)될 때, 브라우저는 이 변수가 나중에 어디서 어떻게 쓰일지 아직 모르기 때문에 일단 거의 모든 값을 유효하다고 가정하고 저장해 둡니다.
문제는 이렇게 무사히 저장된 값들이 나중에 var() 함수를 통해 엉뚱한 맥락(context)에 주입될 때 발생합니다.
변수 안의 엉뚱한 값 때문에 CSS 문장 전체가 유효하지 않게 되어버리는 이런 상황을, CSS 용어로 '계산 시간(computed time)에 유효함'이라고 부릅니다. (즉, 구문 분석 때는 통과했지만 나중에 실제로 계산해보니 에러가 터졌다는 뜻이죠.)

브라우저가 이렇게 무효한 var() 대체를 발견하면, 해당 속성의 초깃값(initial)이나 부모로부터 상속된 값(inherited)으로 롤백해버립니다.

아래 예제를 볼까요? CSS 일반 속성에서 봤던 것과 똑같지만, 이번엔 사용자 지정 속성을 사용했습니다.

브라우저는 color: var(--text-color) 자리에 --text-color의 값인 16px를 대체해 넣습니다. 하지만 16pxcolor 속성에 유효한 값이 아닙니다.
대체가 끝난 직후 이 속성 문장은 완전히 의미를 잃어버리게 되죠. 이 상황에서 브라우저는 다음 두 단계를 거쳐 수습에 나섭니다.

  1. 먼저 color 속성이 상속(inheritable)되는 성질인지 확인합니다. 상속되는 속성이 맞습니다. 하지만 이 <p> 태그의 부모 요소들 중에는 color가 설정된 요소가 하나도 없네요. 그래서 다음 단계로 넘어갑니다.
  2. 부모에게 받을 게 없으니, 속성 고유의 기본 초깃값(default initial value)인 검은색(black)으로 세팅해 버립니다. (그래서 blue가 무시되고 검은색으로 나타나게 됩니다!)
:root {
  --text-color: 16px;
}

p {
  font-weight: bold;
  color: blue;
}

p {
  /* 16px이 주입되고 에러가 터지면서 파란색이 아닌 기본값(검은색)이 되어버림 */
  color: var(--text-color);
}

JavaScript에서의 값 제어 (Values in JavaScript)

JavaScript에서 사용자 지정 속성의 값을 가져오거나 설정하는 방법은 일반 CSS 속성(표준 속성)을 다루는 방법과 완전히 똑같습니다!

// 인라인 스타일에서 변수값 가져오기
element.style.getPropertyValue("--my-var");

// 인라인뿐만 아니라 적용된 전체 스타일에서 최종 계산된 변수값 가져오기
getComputedStyle(element).getPropertyValue("--my-var");

// 인라인 스타일로 변수값 설정하기
element.style.setProperty("--my-var", jsVar + 4);

💡 강사 팁:
setProperty 메서드가 진짜 마법 같은 기능입니다. Next.js나 React 앱을 개발하실 때, 사용자의 취향에 따라 테마 색상을 바꿔주는 기능(예: Color Picker)을 만든다고 가정해 볼게요.
React의 상태 관리 라이브러리(Zustand나 SWR)로 사용자가 고른 색상 값을 가져와서 최상위 document.documentElement.style.setProperty('--main-bg-color', selectedColor) 로 한 방에 쏴주면? 사이트 전체의 디자인 시스템이 리렌더링 없이 한순간에 쫘악~ 부드럽게 바뀌게 됩니다. 꼭 포트폴리오에 적용해 보세요!


참고 자료 (See also)


MDN 개선에 참여하기 (Help improve MDN)

이 페이지가 도움이 되셨나요?
[Yes][No]

기여하는 방법 알아보기(Learn how to contribute)
이 페이지의 마지막 수정일은 2025년 12월 16일이며, MDN 기여자들에 의해 수정되었습니다.

profile
프론트에_가까운_풀스택_개발자

0개의 댓글