
CSS Variables (사용자 지정 속성) - 하드코딩의 늪에서 벗어나 동적 테마를 지배하다
과거 웹 개발자들은 웹사이트 전체의 메인 컬러(예: 브랜드 컬러인 파란색 #1da1f2)를 적용하기 위해 수백 개의 CSS 클래스에 color: #1da1f2;를 복사해서 붙여넣었습니다. 어느 날 기획자가 "브랜드 컬러를 약간 보라색 톤으로 바꿔주세요"라고 요청하면, 개발자는 프로젝트 전체를 뒤져가며 수백 개의 코드를 일일이 찾아 바꾸는(Find & Replace) 지옥 같은 막노동을 해야만 했습니다. 프로그래밍의 제1 원칙인 DRY(Don't Repeat Yourself, 중복 금지)가 완전히 박살 난 상태였죠.
이를 해결하기 위해 Sass(SCSS)나 Less 같은 CSS 전처리기(Preprocessor)들이 등장하여 변수 기능을 제공했지만, 이것들은 컴파일 단계에서 값이 고정되어 버리기 때문에 런타임(브라우저 실행 중)에 자바스크립트로 다크 모드 전환 같은 동적인 변화를 주기가 매우 까다로웠습니다. 그리고 마침내, W3C는 CSS 자체에 프로그래밍 언어의 '변수(Variables)' 개념을 도입한 CSS Custom Properties (사용자 지정 속성)를 표준으로 발표했습니다. 이 혁명적인 기능 덕분에 오늘날 우리는 단 한 줄의 자바스크립트 코드만으로 수만 개의 요소가 얽힌 대형 웹사이트의 테마(다크 모드, 라이트 모드)를 0.01초 만에 스위칭할 수 있게 되었습니다.
이번 글에서는 CSS 변수의 선언과 스코프(Scope) 원리부터, Sass 변수와의 결정적 차이점, 그리고 자바스크립트와의 완벽한 상호작용까지 다뤄보겠습니다.
1. 변수의 탄생: "--" (더블 대시)의 마법
CSS 변수는 이름표를 붙이는 아주 단순한 문법을 가집니다. 변수 이름을 지을 때는 반드시 앞에 하이픈 두 개(--)를 붙여야 합니다. (예: --main-bg-color). 그리고 이 변수에 저장된 값을 꺼내 쓸 때는 var() 함수를 사용합니다. 프로그래밍 언어를 조금이라도 접해본 사람이라면 너무나도 익숙하고 반가운 패턴입니다.
/* 1. 변수를 선언하고 값을 저장합니다 */
:root {
--primary-color: #3498db;
--base-padding: 16px;
}
/* 2. 저장된 변수를 불러와서 재사용합니다 */
.btn {
background-color: var(--primary-color);
padding: var(--base-padding);
}
2. 지배력의 범위: 스코프(Scope)와 :root의 비밀
자바스크립트에 전역 변수(Global)와 지역 변수(Local)가 있듯이, CSS 변수에도 스코프(Scope)라는 유효 범위가 존재합니다. 어디에 선언하느냐에 따라 그 변수를 쓸 수 있는 자식들의 범위가 달라집니다.
전역 변수: :root 가상 클래스
웹사이트의 전체 테마 색상이나 기본 폰트 사이즈 등 모든 페이지, 모든 요소에서 공통으로 끌어다 써야 하는 값은 무조건 :root 가상 클래스 선택자 안에 선언합니다. :root는 HTML 문서의 최상위 뿌리인 <html> 태그와 동일하며, 특이도(Specificity)가 더 높기 때문에 전역 변수를 보관하는 최적의 금고 역할을 합니다. 실무에서는 보통 variables.css라는 파일을 따로 만들어 수십 개의 :root 변수들을 테마 딕셔너리처럼 중앙 관리합니다.
지역 변수와 섀도잉(Shadowing)
특정 컴포넌트 안에서만 쓸 변수라면 해당 클래스 안에 직접 선언할 수 있습니다. 예를 들어 .card { --card-bg: #fff; }라고 선언하면, 이 --card-bg 변수는 .card 자기 자신과 그 뱃속에 있는 자식 요소들만 꺼내 쓸 수 있습니다. 밖에서는 접근할 수 없습니다.
더욱 놀라운 것은 '변수 덮어쓰기(Shadowing)'가 가능하다는 점입니다. :root에 --text-color: black;이 있더라도, 특정 섹션 .dark-section { --text-color: white; }를 선언해 주면, 그 어두운 섹션 안에 있는 자식 요소들이 var(--text-color)를 호출할 때는 흰색으로 바뀌어 적용됩니다. 이 상속(Inheritance)의 원리를 이용하면 엄청나게 우아하고 확장성 있는 컴포넌트 디자인이 가능해집니다.
3. 안전망 구축: Fallback (대체값) 설정
만약 다른 동료 개발자가 실수로 변수 이름을 오타 냈거나, 특정 상황에서 변수에 값이 할당되지 않았다면 어떻게 될까요? 브라우저는 투명하게 변해버리거나 레이아웃을 망가뜨릴 수 있습니다. 이를 방지하기 위해 var() 함수는 두 번째 인자로 '대체값(Fallback)'을 받을 수 있는 강력한 안전장치를 제공합니다.
/* --main-color 변수가 존재하지 않거나 오타가 났다면, 뒤에 있는 #333(진회색)을 대신 적용해라! */
.title {
color: var(--main-color, #333);
}
이 대체값 기능은 특히 여러 개의 테마 파일을 모듈화해서 불러올 때, 변수가 늦게 로딩되더라도 화면이 깨지지 않도록 막아주는 훌륭한 방패막이가 됩니다.
4. CSS 변수의 진정한 파괴력: 자바스크립트(JS)와의 완벽한 소통
CSS 변수가 Sass(SCSS) 같은 전처리기 변수 압도적으로 뛰어넘는 가장 큰 이유가 바로 여기에 있습니다. 순수 CSS 변수는 브라우저의 DOM 트리에 살아 숨 쉬는 속성이기 때문에, 자바스크립트를 사용해 실시간으로 변수의 값을 빼오거나(Get) 새로운 값으로 바꿔치기(Set) 할 수 있습니다.
예를 들어 사용자가 마우스를 움직이는 좌표(X, Y)에 따라 그림자의 방향이 실시간으로 변하는 인터랙티브 버튼을 만든다고 가정해 봅시다. 과거에는 JS에서 버튼의 style.boxShadow를 통째로 매 프레임마다 조작해야 해서 코드가 더럽고 브라우저 렌더링 성능이 심각하게 저하되었습니다. 하지만 이제는 JS에서 단순히 --mouse-x, --mouse-y CSS 변수의 숫자 값만 틱틱 업데이트해주고, CSS에서는 box-shadow: var(--mouse-x) var(--mouse-y) 10px black; 이라고 받아먹기만 하면 끝입니다. 로직(JS)과 디자인(CSS)이 완벽하게 분리되면서도 환상적인 퍼포먼스를 내는 모던 웹 인터랙션의 핵심 비법입니다.
정리하며
이번 글에서는 지옥 같은 하드코딩에서 우리를 구원해 주고, 다크 모드와 실시간 테마 변경이라는 모던 웹의 필수 요소를 단 한 줄의 코드로 구현하게 해주는 기적의 기술, CSS Variables에 대해 깊이 있게 살펴보았습니다. 변수를 통해 CSS 코드가 훨씬 스마트해지고 관리가 편해졌다면, 이제 다시 화면 배치(Layout)의 이야기로 돌아갈 시간입니다. 지난 시간에 배운 float라는 기괴한 해킹 기법의 끔찍함을 기억하시나요? W3C는 그 참상을 두고 볼 수 없었고, 오직 '레이아웃'만을 위해 창조된 완벽하고 아름다운 구세주를 하사하셨습니다. 프론트엔드 개발자들의 빛이자 소금! 다음이번 포스팅에서는 "CSS Flexbox (1편): 부모(Container)의 규칙, 가로세로 1차원 배치의 마법"에 대해 상세히 파헤쳐 보겠습니다!
CSS 변수(Custom Properties) - 전역 테마와 유지보수의 혁명
여러분이 메인 컬러가 파란색(#0055ff)인 대형 웹사이트를 코딩했다고 칩시다. 이 파란색은 버튼, 링크, 헤더 배경, 아이콘 테두리 등 수백 곳에 흩어져 있습니다. 어느 날 사장님이 "우리 회사 로고 색상이 주황색으로 바뀌었으니 사이트 메인 컬러도 다 주황색으로 바꿔줘"라고 지시합니다. CSS 파일 수십 개를 열어서 #0055ff를 찾아 일일이 주황색으로 바꾸는 찾기/바꾸기(Ctrl+F) 노가다를 해야 할까요? 과거에는 그랬습니다. 하지만 오늘날 현대 프론트엔드 개발자들은 이런 지시를 받으면 단 한 줄의 코드만 수정하고 1초 만에 퇴근합니다. 이 마법을 가능하게 해주는 기술이 바로 CSS 변수(Variables, 공식 명칭 Custom Properties)입니다. 프로그래밍 언어의 '변수' 개념을 순수 CSS에 도입하여 색상, 폰트, 여백 등 반복되는 수치들을 중앙집중식으로 관리하게 해주는 엄청난 권력! 이번 글에서는 CSS 변수의 선언과 사용법, 그리고 자바스크립트와의 환상적인 연동을 통한 실무 테마 제어 비법을 상세히 알아보겠습니다.
1. 선언과 호출: 마법의 시작
CSS 변수는 변수명 앞에 반드시 하이픈 두 개(--)를 붙여서 선언해야 합니다. 그리고 이를 사용할 때는 var() 함수를 사용하여 불러옵니다.
변수를 담는 거대한 그릇: :root
보통 웹사이트 전체에서 공통으로 쓸 전역(Global) 변수들은 :root 가상 클래스(HTML 문서의 최상위 뿌리) 안에 모아둡니다. 이곳이 바로 웹사이트의 '테마 설정 컨트롤 타워'가 됩니다.
/* 1단계: 통제실(:root)에 변수를 선언하고 값을 저장합니다 */
:root {
--primary-color: #0055ff; /* 브랜드 메인 파란색 */
--danger-color: #ff3333; /* 에러/삭제 빨간색 */
--base-spacing: 16px; /* 기본 여백 크기 */
}
/* 2단계: 실무 코딩 시 색상 코드 대신 var() 함수를 불러다 씁니다 */
.btn-submit {
background-color: var(--primary-color);
padding: var(--base-spacing);
}
.error-text {
color: var(--danger-color);
}
이제 사장님이 메인 컬러를 주황색으로 바꾸라고 하면, 우리는 수백 개의 코드를 건드릴 필요 없이 오직 :root 안의 --primary-color: #ff6600; 딱 한 줄만 수정하면 끝납니다!
2. 💡 실무 꿀팁: 폴백(Fallback) 값 제공하기
변수 이름에 오타가 났거나 특정 상황에서 변수 값이 비어있을 때 디자인이 깨지는 것을 막기 위해, var() 함수에 두 번째 파라미터로 '비상용 대체 값(Fallback)'을 적어둘 수 있습니다.
.btn {
/* 만약 --theme-color 변수가 없으면 무조건 black을 적용해라! */
background-color: var(--theme-color, black);
}
3. CSS 변수가 Sass(SCSS) 변수보다 압도적으로 뛰어난 이유
과거 전처리언어인 SCSS 환경에서도 $primary-color: blue; 같은 변수 문법이 존재했습니다. 하지만 SCSS 변수는 컴퓨터가 렌더링 되기 전(Compile 타임)에 단순한 글자로 치환되어 사라지는 가짜 변수입니다. 반면 CSS 순수 변수(Custom Properties)는 브라우저가 읽고 메모리에 올려두는 진짜(Runtime) 변수입니다.
자바스크립트(JS)로 CSS 변수 실시간 조종하기
이것이 CSS 변수의 궁극적인 무기입니다. JS를 통해 브라우저가 실행되는 도중(런타임)에 CSS 변수 값을 실시간으로 바꿔치기할 수 있습니다!
// JS 코드 한 줄로 :root의 메인 컬러 변수를 초록색으로 엎어버립니다!
document.documentElement.style.setProperty('--primary-color', '#00ff00');
사용자가 마우스를 움직일 때 그 X, Y 좌표값을 JS가 CSS 변수 --mouse-x에 실시간으로 꽂아주고, CSS에서는 그 변수를 받아 그림자나 그라데이션 애니메이션을 그리는 엄청난 마이크로 인터랙션 구현이 가능해집니다.
정리하며
CSS 변수를 도입하는 순간, 여러분의 코드는 '하드코딩된 노가다의 산물'에서 '우아하고 체계적인 디자인 시스템(Design System)'으로 승격됩니다. 이 중앙집중식 통제탑을 세웠다면, 이제 이것을 활용하여 전 세계 모든 웹 서비스의 필수 스펙이 된 거대한 트렌드를 정복할 차례입니다. 버튼 한 번 클릭에 사이트 전체가 어둠의 자식으로 변신하는 그 기술! 다음이번 포스팅에서는 CSS 변수를 극한으로 활용하는 "다크 모드(Dark Mode) 완벽 구현 가이드"에 대해 상세히 파헤쳐 보겠습니다!
모던 CSS의 대혁명 - SCSS를 위협하는 Native Nesting과 기적의 선택자 :is(), :where(), Subgrid
지난 10여 년간 프론트엔드 개발자들에게 Sass(SCSS)나 Less 같은 'CSS 전처리기(Preprocessor)'는 선택이 아닌 필수였습니다. 순수 CSS(Vanilla CSS)는 중첩(Nesting)을 지원하지 않아서 .card .title, .card .desc, .card .btn처럼 부모 클래스 이름을 수십 번 앵무새처럼 반복 타이핑해야 하는 끔찍한 막노동을 강요했기 때문입니다. 개발자들은 SCSS를 설치해 .card { .title {...} } 처럼 괄호 안에 괄호를 넣는 프로그래밍적인 중첩 문법의 달콤함에 빠졌고, 복잡한 노드(Node.js) 빌드 시스템을 억지로 구축하면서까지 전처리기를 고집했습니다. 하지만 W3C 표준 위원회는 이 상황을 좌시하지 않았습니다. 바야흐로 2023년 말부터 2024년에 걸쳐, SCSS의 전유물이었던 기능들을 순수 CSS 표준(Native) 문법으로 통째로 흡수해 버리는 역대급 대혁명 패치가 모든 모던 브라우저에 배포되었습니다. 이제 우리는 무거운 Node 빌드 과정이나 추가 라이브러리 설치 없이도, .css 확장자 파일 그 자체만으로 SCSS 뺨치는 강력하고 콤팩트한 코딩을 할 수 있게 되었습니다. 이번 글에서는 CSS 생태계를 발칵 뒤집어 놓은 Native Nesting (네이티브 중첩)의 등장과, 끝도 없이 길어지던 다중 선택자를 팩맨처럼 한 입에 먹어치우는 :is(), :where() 함수형 선택자, 그리고 Grid의 마지막 한계를 박살 내버린 Subgrid에 대해 상세히 알아보겠습니다.
1. SCSS여 안녕: Native CSS Nesting (네이티브 중첩)
네이티브 네스팅의 등장으로 이제 .css 파일 안에서도 SCSS와 99% 똑같은 방식으로 코드를 중첩해서 짤 수 있게 되었습니다. 부모 선택자를 반복해서 적을 필요 없이, 중괄호 {} 안에 자식 선택자를 집어넣기만 하면 브라우저가 알아서 계층 구조로 해석(파싱)해 줍니다.
/* 과거의 끔찍했던 순수 CSS 생노가다 */
.card { background: white; padding: 20px; }
.card .title { font-size: 20px; color: black; }
.card .title:hover { color: blue; }
/* 🚀 2024년 모던 CSS (Native Nesting)의 기적 */
.card {
background: white;
padding: 20px;
/* 괄호 안에 그냥 자식을 쓰면 됩니다! */
.title {
font-size: 20px;
color: black;
/* 앰퍼샌드(&)를 써서 hover 가상 클래스까지 딱 달라붙게 처리! */
&:hover {
color: blue;
}
}
}
SCSS와 문법이 사실상 완전히 동일하며, 기괴한 BEM 네이밍(&__title) 결합 같은 극히 일부의 문자열 병합 기능을 제외하면 실무에서 요구하는 중첩의 90% 이상을 브라우저가 네이티브로 초고속 처리해 냅니다. 번들러(Webpack, Vite) 세팅에 지친 프론트엔드 생태계에 내린 단비 같은 축복입니다.
2. 콤보 선택자의 혁명: :is() 와 :where()
게시판을 짤 때 "헤더 안에 있는 링크(a), 푸터 안에 있는 링크, 사이드바 안에 있는 링크를 전부 회색으로 만들어라!"라는 디자인을 적용하려면 어떻게 짰을까요? header a, footer a, sidebar a { color: gray; } 처럼 끝도 없이 길어지는 콤마(,) 리스트를 타이핑해야 했습니다. 만약 li 태그까지 끼어들면 구문은 산으로 갑니다. 이를 한 방에 압축해 주는 함수형 선택자가 바로 :is() 입니다.
/* 💡 구질구질한 반복을 팩맨처럼 하나로 묶어버리는 :is() */
:is(header, footer, sidebar) ul li a {
color: gray;
}
/* 해석: (헤더이거나 푸터이거나 사이드바) 뱃속에 있는 ul > li > a 를 전부 잡아라! */
구체성(Specificity)의 마술사: :where()
:is()와 문법과 역할이 100% 똑같은 쌍둥이 동생으로 :where()가 있습니다. 하지만 이 둘은 '구체성(우선순위 점수)'에서 하늘과 땅 차이를 보입니다. :is()는 괄호 안에 들어간 녀석들 중 가장 점수가 쎈 놈의 파워를 전체가 상속받아 버려서 나중에 다른 코드로 덮어쓰기(Override)가 굉장히 빡세집니다.
반면 :where()는 구체성 점수가 '0점(Zero)'입니다. 괄호 안에 #id 같은 초강력 선택자를 쑤셔 넣어도, :where() 필터를 거치는 순간 파워가 0점으로 초기화됩니다. 이 성질을 이용해 CSS 프레임워크나 라이브러리를 만들 때 "내가 기본 스타일을 예쁘게 잡아주긴 할 건데(0점 부여), 언제든지 네가 아주 약한 클래스 하나만 써도 쉽게 덮어쓸 수 있게 해줄게!"라는 배려 깊은 디폴트 스타일링(CSS 초기화)을 짤 때 신의 한 수로 쓰입니다.
3. 격자의 혈통을 계승하라: CSS Subgrid
이전 글에서 CSS Grid가 세상의 모든 레이아웃을 씹어먹을 궁극의 2차원 바둑판이라고 칭송했습니다. 하지만 Grid에는 '뼈아픈 치명상'이 하나 있었습니다. 부모가 쳐놓은 바둑판(Grid)의 선(Line)들은 오직 '직계 자식'에게만 영향을 미칠 뿐, '손자' 요소들에게는 전달되지 않는다는 것입니다.
상품 카드 3개가 나란히 배치된 리스트가 있습니다. 각 카드는 뱃속에 [사진, 제목, 버튼]이라는 손자 요소들을 가지고 있죠. 첫 번째 상품은 제목이 엄청 깁니다(두 줄). 두 번째 상품은 제목이 짧습니다(한 줄). 이 상태로 Grid를 배치하면, 상품 카드들의 전체 높이는 똑같이 맞춰지지만, 그 안(손자 뎁스)에 들어있는 '버튼'들의 높낮이는 자기들 맘대로 삐뚤빼뚤하게 배치되는 끔찍한 UI 깨짐이 발생했습니다. 직계 자식인 '카드'라는 밀폐용기가 할아버지의 바둑판 선을 막아버렸기 때문입니다.
이 재앙을 부수고, 할아버지의 바둑판 선을 손자 뱃속까지 그대로 투과시켜버리는 마법이 바로 grid-template-rows: subgrid; 입니다. 자식(카드)에게 서브그리드를 선언하는 순간, 손자들(사진, 제목, 버튼)은 자신들의 고유한 크기를 버리고 할아버지가 그어놓은 거대한 글로벌 바둑판 선에 강제로 자신의 위치와 높낮이를 맞추게 됩니다. 이로써 제목 길이가 10줄이든 1줄이든 상관없이 모든 카드의 하단 구매 버튼이 1픽셀의 오차도 없이 일직선으로 완벽하게 오와 열을 맞추는 기적의 반응형 쇼핑몰 UI가 탄생하게 됩니다.
정리하며
이번 글에서는 SCSS의 입지를 맹렬히 위협하는 Native Nesting의 편안함과, 반복 코드를 팩맨처럼 압축하는 :is()와 :where(), 그리고 다단계 그리드 지옥을 평정해 버린 구세주 Subgrid까지 모던 CSS의 최전선 트렌드를 상세히 살펴보았습니다. 최신 기술의 발전 속도는 눈부시지만, 여전히 실무에서는 오래된 브라우저(레거시)를 지원하거나 좀 더 복잡한 변수 연산(for문 루프 등)을 처리하기 위해 컴파일형 전처리기의 힘을 빌려야 하는 순간이 존재합니다. 프로그래머들의 로망을 CSS에 구현해 낸 오리지널 대장! 다음
'StyleSheet > CSS' 카테고리의 다른 글
| [CSS] 현대적인 CSS 프레임워크와 도구 (0) | 2026.08.10 |
|---|---|
| [CSS] CSS 아키텍처와 방법론 (BEM, Sass/SCSS) (0) | 2026.08.10 |
| [CSS] CSS 애니메이션 완벽 가이드 (Transform, Transition, Keyframes) (0) | 2026.08.10 |
| [CSS] 시각적 효과와 꾸미기 (그림자, 필터, 다크 모드) (0) | 2026.08.10 |
| [CSS] 반응형 웹디자인 (미디어 쿼리, 컨테이너 쿼리) (0) | 2026.08.10 |