다크 모드를 붙이다 보면 늘 같은 코드가 반복된다. 색상 하나마다 라이트용 값과 다크용 값을 따로 선언하고, .dark 클래스나 미디어 쿼리로 덮어쓴다. 변수가 스무 개쯤 되면 같은 이름을 두 번씩 적게 되고, 나중에 하나 바꿀 때 두 군데를 손대야 하는 게 은근히 귀찮다.
:root {
--bg: white;
--text: #1a1a1a;
}
@media (prefers-color-scheme: dark) {
:root {
--bg: #1a1a1a;
--text: #f0f0f0;
}
}
light-dark() 함수를 쓰면 이걸 한 줄로 합칠 수 있다.
color-scheme부터 켜야 한다
light-dark()는 요소가 현재 어떤 스킴인지를 color-scheme 속성으로 판단한다. 그래서 이 속성을 먼저 선언해줘야 함수가 동작한다. light dark 둘 다 적으면 사용자 OS 설정을 따라간다.
:root {
color-scheme: light dark;
--bg: light-dark(white, #1a1a1a);
--text: light-dark(#1a1a1a, #f0f0f0);
--accent: light-dark(#2563eb, #60a5fa);
}
body {
background: var(--bg);
color: var(--text);
}
첫 번째 인자가 라이트 값, 두 번째가 다크 값이다. 미디어 쿼리 블록이 통째로 사라지고, 색 하나가 한 줄에 다 담긴다. 값을 바꿀 때도 그 줄만 고치면 된다.
덤으로 color-scheme을 선언하면 폼 컨트롤, 스크롤바 같은 브라우저 기본 UI도 다크에 맞춰 자동으로 바뀐다. 예전엔 스크롤바가 하얗게 붕 뜨는 걸 따로 잡아줬는데 그럴 필요가 없어진다.
사용자가 직접 토글하게 하기
OS 설정이 아니라 앱 안에서 테마 버튼으로 전환하고 싶다면, color-scheme 값을 light 또는 dark 하나로 고정하면 된다. 보통은 최상위에 data-theme 속성을 걸고 CSS로 매핑한다.
:root[data-theme='light'] { color-scheme: light; }
:root[data-theme='dark'] { color-scheme: dark; }
// 버튼 클릭 핸들러
document.documentElement.dataset.theme = isDark ? 'dark' : 'light';
light-dark()로 정의한 변수들은 손댈 필요가 없다. color-scheme 값만 바뀌면 모든 색이 알아서 따라온다.
주의할 점
light-dark()는 색상 값에만 쓸 수 있다. background-image나 크기 값처럼 색이 아닌 곳엔 못 넣는다. 그리고 Baseline에 들어온 지 얼마 안 된 기능이라 아주 오래된 브라우저까지 챙겨야 하면 @supports (color: light-dark(#000, #fff))로 감싸서 폴백을 두는 게 안전하다.
정리하면, color-scheme: light dark 한 줄 켜두고 색을 전부 light-dark()로 선언하면 다크 모드 코드가 절반으로 준다. .dark 클래스로 값을 통째로 다시 쓰던 방식에서 벗어나고 싶다면 한 번 갈아타 볼 만하다.