이번 편의 결과물: 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.css의 tokens 레이어 블록을 엽니다.
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. 확인
- 개발자 도구
Elements→Styles패널에서:root를 선택하면--color-*,--space-*,--font-*순서로 그룹지어진 커스텀 속성 목록이 보입니다. - 콘솔에서
getComputedStyle(document.documentElement).getPropertyValue('--color-badge-bg')를 입력하면 빈 문자열이 나옵니다. 하지만.project-card-badge의 실제 배경색은 폴백 값(--color-surface)으로 정상 표시됩니다. - 콘솔에서
document.documentElement.style.setProperty('--color-primary', 'red')를 입력하면 히어로 버튼과 링크 색이 즉시 빨간색으로 바뀝니다. 별도 빌드나 새로고침이 필요 없습니다.
직접 해보기
.contact-form에--color-badge-bg처럼 전용 토큰이 없는 상태 표시 색(예: 제출 성공 메시지 배경)을 폴백 값과 함께 추가해 보세요..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-* 등 이번 편 표의 접두사·소문자 하이픈 규칙을 지킨다 |