다크모드는 색을 뒤집는 게 아니라 다시 짜는 일입니다. 흰 배경을 검게 칠하고 검은 글씨를 희게 바꾸기만 하면 눈이 부시고 색이 튀어 오릅니다. 토큰을 갈아 끼우는 방식으로 접근합니다. 아래 예제는 그대로 확인할 수 있습니다.

52.1 시스템 설정 읽기

사용자가 기기에서 어두운 테마를 켰는지 미디어쿼리로 알 수 있습니다. 이 안에서 변수만 바꾸면 화면 전체가 따라옵니다.


:root {
--bg: #ffffff;
--text: #1f2937;
}
@media (prefers-color-scheme: dark) {
:root {
--bg: #111827;
--text: #e5e7eb;
}
}
body { background: var(--bg); color: var(--text); }


스타일 규칙은 변수만 바라보고, 값은 미디어쿼리 안에서 갈립니다. body 아래 어떤 요소도 건드리지 않고 테마가 바뀝니다.

52.2 순수 검정과 순수 흰색 피하기

배경을 완전한 검정, 글자를 완전한 흰색으로 두면 대비가 너무 세서 눈이 아픕니다. 양쪽을 살짝 안으로 들여옵니다.


/* 나쁜 예 */
.bad { background: #000000; color: #ffffff; }
/* 좋은 예: 톤을 살짝 준 어둠과 밝음 */
.good {
background: #14181f;
color: #dfe3ea;
}


완전한 흰 글씨는 검은 배경에서 번져 보입니다. 명도를 조금만 낮춘 밝은 회색이 오래 봐도 편합니다.

52.3 어두운 배경에선 채도 낮추기

밝은 화면에서 쨍하던 원색이 어둠 위에서는 형광펜처럼 떠오릅니다. 다크에서는 같은 색이라도 채도를 내려 줍니다.


:root {
--brand: hsl(221 83% 53%);
}
@media (prefers-color-scheme: dark) {
:root {
--brand: hsl(221 60% 62%); /* 채도 낮추고 명도 살짝 올림 */
}
}


채도를 83에서 60으로 내리고 명도는 조금 올렸습니다. 어두운 바탕에서 눈을 찌르지 않으면서도 파랑임은 분명합니다.

52.4 표면을 명도로 쌓기

밝은 테마는 그림자로 층을 표현하지만 다크에서는 그림자가 잘 안 보입니다. 위로 올라온 면일수록 배경을 살짝 밝혀 깊이를 줍니다.


@media (prefers-color-scheme: dark) {
:root {
--surface-0: #111827; /* 페이지 바닥 */
--surface-1: #1b2432; /* 카드 */
--surface-2: #26303f; /* 팝오버 */
}
}
.page { background: var(--surface-0); }
.card { background: var(--surface-1); }
.popup { background: var(--surface-2); }


바닥에서 멀어질수록 밝아집니다. 빛이 위에서 온다는 감각을 명도 계단으로 흉내 낸 셈이라, 겹친 요소가 또렷이 구분됩니다.

52.5 겹침을 color-mix로

표면 단계를 일일이 hex로 적기 번거로우면 바닥색에 흰색을 조금씩 섞어 뽑습니다. 섞는 비율이 곧 층의 높이가 됩니다.


@media (prefers-color-scheme: dark) {
:root {
--surface-0: #111827;
--surface-1: color-mix(in oklab, var(--surface-0), white 5%);
--surface-2: color-mix(in oklab, var(--surface-0), white 10%);
}
}


흰색을 5퍼센트, 10퍼센트씩 섞어 자연스럽게 밝아지는 계단을 만들었습니다. 바닥색만 바꾸면 위 단계가 알아서 따라옵니다.

52.6 라이트 다크 토큰 한 쌍

색 이름을 역할로 통일해 두면 두 테마가 같은 이름의 다른 값이 됩니다. 화면 코드는 어느 테마인지 몰라도 됩니다.


:root {
--bg: #ffffff;
--text: #1f2937;
--border: #e5e7eb;
--brand: hsl(221 83% 53%);
}
@media (prefers-color-scheme: dark) {
:root {
--bg: #111827;
--text: #e5e7eb;
--border: #2b3648;
--brand: hsl(221 60% 62%);
}
}
.card { background: var(--bg); color: var(--text); border: 1px solid var(--border); }


카드 스타일은 한 벌인데 테마마다 옷을 갈아입습니다. 새 컴포넌트를 만들어도 이 토큰만 쓰면 다크가 저절로 됩니다.

52.7 사용자가 직접 고르게

시스템 설정과 무관하게 사용자가 테마를 고르고 싶을 때가 있습니다. 최상위 요소에 표시를 달아 수동으로 덮어씁니다.


[data-theme="dark"] {
--bg: #111827;
--text: #e5e7eb;
}
[data-theme="light"] {
--bg: #ffffff;
--text: #1f2937;
}


html 태그에 data-theme 값을 심으면 시스템 설정을 이깁니다. 폼 요소까지 테마에 맞추려면 color-scheme 속성도 함께 선언해 두면 스크롤바와 기본 위젯이 따라옵니다.


:root { color-scheme: light dark; }


이 한 줄로 입력창, 스크롤바 같은 브라우저 기본 UI가 테마에 맞춰 어두워집니다.

52.8 테두리와 그림자 다시 짜기

밝은 테마의 옅은 회색 테두리는 어두운 배경에서 사라집니다. 다크에서는 테두리를 밝은 쪽으로, 그림자는 더 짙게 조정합니다.


.card {
border: 1px solid #e5e7eb;
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
}
@media (prefers-color-scheme: dark) {
.card {
border-color: hsl(221 14% 26%);
box-shadow: 0 1px 3px rgba(0,0,0,0.5);
}
}


테두리는 배경보다 밝아야 보이고, 그림자는 어두운 바탕에서 더 진해야 존재감이 남습니다. 밝은 테마 값을 그대로 쓰면 카드 경계가 뭉개집니다.

52.9 이미지와 아이콘 다루기

흰 배경을 전제로 만든 로고나 아이콘은 어두운 화면에서 겉돕니다. 밝기를 살짝 낮추거나 다크용 자산으로 바꿉니다.


@media (prefers-color-scheme: dark) {
.photo { filter: brightness(0.85); }
.logo-light { display: none; }
.logo-dark { display: block; }
}
.logo-dark { display: none; }


사진은 밝기를 조금 낮춰 눈부심을 줄이고, 로고는 아예 어두운 배경용 버전으로 교체합니다. 색만 바꾼다고 이미지까지 따라오지는 않습니다.

52.10 정리

다크모드는 반전이 아니라 두 번째 팔레트입니다. 순수 검정과 흰색을 피하고, 채도를 낮추고, 명도로 표면을 쌓고, 역할 토큰을 두 벌로 준비하면 어두운 화면도 낮처럼 정돈됩니다. 시스템 설정을 기본으로 두되 사용자가 직접 고를 길도 열어 두면 그걸로 충분합니다.