Skip to Content
WebCSS모던 CSS10. 커스텀 속성으로 디자인 토큰 설계하기

이번 편의 결과물: tokens 레이어에 --color-*, --space-*, --font-* 접두사로 이름을 통일한 커스텀 속성 토큰 세트가 생기고, 각 토큰에 폴백 값이 붙는다. · 다루는 개념: 커스텀 속성 상속과 재정의, var() 폴백 값 문법, 토큰 이름 체계

이 편에서 만드는 파일

web-practice/ └── portfolio/ └── css/ └── styles.css ~ (tokens 레이어를 이름 체계에 맞게 재정리)

css_fundamentals에서 만든 --color-bg, --text-lg, --space-4 같은 값은 이미 있습니다. 이번 편은 값을 새로 만드는 게 아니라 이름 체계를 세우고 tokens 레이어 안에 정리합니다.

개념 정리

커스텀 속성(--이름)은 일반 CSS 속성처럼 상속되고 캐스케이드를 따릅니다. :root에 선언하면 모든 요소가 물려받고, 특정 요소 안에서 같은 이름을 다시 선언하면 그 요소와 자손만 새 값을 씁니다.

:root { --color-primary: #0969da; } .hero { --color-primary: #bf3989; /* .hero 안에서만 재정의 */ }

.hero 밖에서는 #0969da, 안에서는 #bf3989로 계산됩니다. 이 성질을 이용해 “컴포넌트 안에서만 토큰 값을 바꾼다”는 실습을 11~13편에서 계속 씁니다.

var() 폴백 값

var(--토큰, 대체값)처럼 두 번째 인자를 주면, 그 토큰이 정의되지 않았을 때 대체값을 씁니다.

.badge { background: var(--color-badge-bg, var(--color-surface)); }

--color-badge-bg가 어디에도 선언되지 않았다면 --color-surface 값을 씁니다. 폴백은 값을 하나만 줄 수도, 다른 var()를 중첩해 넣을 수도 있습니다.

토큰 이름 체계

지금까지 이 사이트의 커스텀 속성은 tokens.css에 있던 것을 그대로 썼습니다. 이번 편부터는 접두사로 역할을 구분합니다.

접두사용도예시
--color-*색(배경·글자·테두리·강조)--color-bg, --color-text-muted, --color-border
--space-*간격(margin·padding·gap)--space-4, --space-8
--font-*글꼴 계열--font-sans, --font-mono
--text-*글자 크기--text-sm, --text-3xl
--radius-*모서리 반경--radius(기본값 하나만 쓰는 사이트는 접미사 생략 가능)
--shadow-*그림자--shadow

접두사 뒤에 역할을 붙이는 두 번째 규칙도 정합니다. --color-{역할} 또는 --color-{역할}-{상태} 형태로, 상태는 hover·muted·disabled 같은 단어를 씁니다. 이 규칙은 12편에서 color-mix()--color-primary-hover를 자동 계산할 때 그대로 이어집니다.

흔한 오해

커스텀 속성을 Sass 변수와 같은 것으로 착각하기 쉽습니다. Sass 변수는 컴파일 시점에 값으로 치환되어 사라지지만, 커스텀 속성은 브라우저가 런타임에 계산합니다. 그래서 :root나 특정 요소 안에서 값을 다시 선언하면, 별도 빌드 없이 그 순간 화면이 바뀝니다. 13편의 다크모드 토글이 가능한 이유도 이 런타임 특성 때문입니다.

실습

1. 파일 열기

portfolio/css/styles.csstokens 레이어 블록을 엽니다.

2. 코드 작성

기존 토큰을 접두사 규칙에 맞춰 정리합니다. 이름이 바뀌는 토큰은 없고, 순서와 그룹을 이름 체계대로 다시 묶습니다.

/* portfolio/css/styles.css */ @layer tokens { :root { /* --color-* : 색 */ --color-bg: #ffffff; --color-surface: #f6f7f9; --color-text: #1f2328; --color-text-muted: #59636e; --color-primary: #0969da; --color-primary-hover: #0550ae; --color-border: #d0d7de; --color-accent: #bf3989; /* --font-* : 글꼴 계열 */ --font-sans: 'Pretendard', 'Noto Sans KR', system-ui, sans-serif; --font-mono: 'JetBrains Mono', ui-monospace, monospace; /* --text-* : 글자 크기 */ --text-xs: 0.75rem; --text-sm: 0.875rem; --text-base: 1rem; --text-lg: 1.25rem; --text-xl: 1.5rem; --text-2xl: 2rem; --text-3xl: 3rem; --leading: 1.6; /* --space-* : 간격(4px 배수) */ --space-1: 0.25rem; --space-2: 0.5rem; --space-3: 0.75rem; --space-4: 1rem; --space-6: 1.5rem; --space-8: 2rem; --space-12: 3rem; --space-16: 4rem; /* --radius-* / --shadow-* : 모서리·그림자 */ --radius: 0.5rem; --shadow: 0 1px 3px rgb(0 0 0 / 0.1), 0 4px 12px rgb(0 0 0 / 0.06); /* 레이아웃 값(접두사 없이 이름 그대로 유지) */ --container-max: 72rem; --header-height: 4rem; } }

components 레이어에서 정의되지 않은 토큰을 참조할 때는 폴백을 붙입니다. 프로젝트 카드의 강조 배지처럼 아직 전용 토큰이 없는 곳에 적용합니다. .project-card-badge는 아직 마크업에 없는 새 요소이므로, 실습할 때는 .project-card 안에 <span class="project-card-badge">NEW</span>를 하나 추가해 시각적으로 확인합니다(BEM 접미사가 아니라 하이픈으로 이어 붙인 새 클래스 이름이라는 점에 유의합니다).

/* portfolio/css/styles.css */ @layer components { .project-card-badge { background: var(--color-badge-bg, var(--color-surface)); color: var(--color-badge-text, var(--color-text)); } }

3. 실행

브라우저에서 portfolio/projects.html을 새로고침합니다.

4. 확인

  • 개발자 도구 ElementsStyles 패널에서 :root를 선택하면 --color-*, --space-*, --font-* 순서로 그룹지어진 커스텀 속성 목록이 보입니다.
  • 콘솔에서 getComputedStyle(document.documentElement).getPropertyValue('--color-badge-bg')를 입력하면 빈 문자열이 나옵니다. 하지만 .project-card-badge의 실제 배경색은 폴백 값(--color-surface)으로 정상 표시됩니다.
  • 콘솔에서 document.documentElement.style.setProperty('--color-primary', 'red')를 입력하면 히어로 버튼과 링크 색이 즉시 빨간색으로 바뀝니다. 별도 빌드나 새로고침이 필요 없습니다.

직접 해보기

  1. .contact-form--color-badge-bg처럼 전용 토큰이 없는 상태 표시 색(예: 제출 성공 메시지 배경)을 폴백 값과 함께 추가해 보세요.
  2. .site-footer 안에서만 --color-text-muted를 다른 값으로 재정의해 푸터 글자만 더 흐리게 만들어 보세요. 다른 영역에 영향이 없는지 확인합니다.

답 보기

.form-success { background: var(--color-success-bg, var(--color-surface)); color: var(--color-success-text, var(--color-text)); } .site-footer { --color-text-muted: #8b949e; }

자주 하는 실수

증상원인고치는 법
var(--color-Primary)가 동작하지 않는다커스텀 속성 이름은 대소문자를 구분한다선언한 이름(--color-primary)과 정확히 똑같이 쓴다
컴포넌트 안에서 재정의한 토큰이 다른 컴포넌트에도 적용된다:root가 아니라 공통 부모 요소에 재정의해서 그 아래 모든 자손이 물려받았다영향 범위를 원하는 컴포넌트 클래스 선택자 안으로 좁힌다
폴백 값이 있는데도 브라우저가 값을 못 찾는다는 경고가 뜬다var() 폴백은 토큰이 선언되지 않았을 때만 쓰인다. 빈 문자열로 선언된 토큰은 폴백을 타지 않는다토큰을 아예 선언하지 않거나, 조건부로 선언 자체를 빼는 방식으로 폴백을 유도한다
토큰 이름에 접두사 규칙을 안 지켜 나중에 검색이 어렵다--btnBg처럼 카멜케이스나 축약을 섞어 씀--color-*, --space-* 등 이번 편 표의 접두사·소문자 하이픈 규칙을 지킨다

확인 문제

문제 14지선다
커스텀 속성과 Sass 변수의 가장 큰 차이는?
문제 24지선다
var(--color-badge-bg, var(--color-surface)) 에서 --color-badge-bg가 선언되지 않았다면 어떤 값이 쓰이나?
문제 34지선다
.hero 선택자 안에서 --color-primary를 다시 선언하면?

참고 자료

Last updated on