다크 모드 구현: 전체 개발자 가이드
Embed This Widget
Add the script tag and a data attribute to embed this widget.
Embed via iframe for maximum compatibility.
<iframe src="https://colorfyi.com/iframe/entity//" width="420" height="400" frameborder="0" style="border:0;border-radius:10px;max-width:100%" loading="lazy"></iframe>
Paste this URL in WordPress, Medium, or any oEmbed-compatible platform.
https://colorfyi.com/entity//
Add a dynamic SVG badge to your README or docs.
[](https://colorfyi.com/entity//)
Use the native HTML custom element.
다크 모드는 틈새 기본 설정에서 예상되는 기능으로 이동했습니다. macOS, Windows, Android, iOS 등 모든 플랫폼의 사용자는 시스템 전체에 어두운 모양을 설정할 수 있으며 웹사이트와 앱이 이를 준수할 것으로 기대합니다. 다크 모드를 올바르게 구현하려면 흰색을 검정색으로 바꾸는 것 이상이 필요합니다. 즉, 색상, 대비 및 사용자 제어에 대한 체계적인 접근 방식이 필요합니다. 이 가이드는 CSS 아키텍처부터 JavaScript 토글 메커니즘을 거쳐 두 테마를 철저하게 테스트하는 전체 프로세스를 안내합니다.
테마용 CSS 사용자 정의 속성
CSS에서 다크 모드를 처리하는 가장 유지 관리하기 쉬운 방법은 사용자 정의 속성(CSS 변수라고도 함)을 사용하는 것입니다. 스타일시트 전체에 색상 값을 분산시키는 대신 모든 색상을 :root의 변수로 정의한 다음 해당 변수를 다크 모드에 맞게 다시 정의합니다. 구성요소 스타일은 변수만 참조하며 원시 16진수 코드는 참조하지 않습니다.
밝은 팔레트와 어두운 팔레트 정의하기
기본적으로 라이트 모드 팔레트로 시작하세요. 깔끔한 시작점은 다음과 같습니다.
:root {
/* Backgrounds */
--color-bg-base: #FFFFFF;
--color-bg-elevated: #F8F9FA;
--color-bg-overlay: #F1F3F5;
/* Text */
--color-text-primary: #1A1A2E;
--color-text-secondary: #4A4A6A;
--color-text-muted: #6C757D;
/* Borders */
--color-border: #DEE2E6;
--color-border-strong: #ADB5BD;
/* Brand / accent */
--color-accent: #3B82F6;
--color-accent-hover: #2563EB;
/* Feedback */
--color-success: #22C55E;
--color-warning: #F59E0B;
--color-danger: #EF4444;
}
그런 다음 별도의 블록에서 어두운 모드에 대한 재정의를 정의합니다. 핵심 통찰력은 단순히 색상을 반전시키는 것이 아니라 어두운 표면을 위해 특별히 제작된 다른 팔레트를 선택한다는 것입니다.
[data-theme="dark"] {
/* Backgrounds */
--color-bg-base: #0F0F17;
--color-bg-elevated: #1A1A2E;
--color-bg-overlay: #252540;
/* Text */
--color-text-primary: #E8E8F0;
--color-text-secondary: #A8A8C0;
--color-text-muted: #6A6A88;
/* Borders */
--color-border: #2E2E4A;
--color-border-strong: #4A4A6A;
/* Brand / accent — often slightly lighter for legibility on dark bg */
--color-accent: #60A5FA;
--color-accent-hover: #93C5FD;
/* Feedback — slightly desaturated to avoid harshness */
--color-success: #4ADE80;
--color-warning: #FCD34D;
--color-danger: #F87171;
}
밝은 모드의 악센트 #3B82F6는 어두운 모드에서 #60A5FA가 됩니다. 색조는 동일하지만 밝기가 증가합니다. 이는 대비 상황이 반전되었기 때문에 필요합니다. 흰색 배경에 대해 WCAG AA를 통과하는 색상은 조정하지 않는 한 검정색에 가까운 배경에 대해 거의 항상 실패합니다. Shade Generator를 사용하면 모든 색상의 전체 50~950 범위를 탐색하여 각 테마에 적합한 색상을 쉽게 선택할 수 있습니다.
구성 요소에서 변수 사용
팔레트가 설정되면 모든 구성 요소는 원시 값이 아닌 변수를 참조합니다.
.card {
background-color: var(--color-bg-elevated);
border: 1px solid var(--color-border);
color: var(--color-text-primary);
}
.btn-primary {
background-color: var(--color-accent);
color: #FFFFFF;
}
.btn-primary:hover {
background-color: var(--color-accent-hover);
}
[data-theme="dark"] 속성이 <html> 요소에 있으면 모든 변수가 동시에 업데이트되고 이를 참조하는 모든 구성 요소의 모양이 변경됩니다. 추가 CSS가 필요하지 않습니다.
prefers-color-scheme 미디어 쿼리
사용자가 토글과 상호 작용하기 전에 prefers-color-scheme 미디어 쿼리를 사용하여 운영 체제 기본 설정을 존중할 수 있습니다. 이 미디어 쿼리는 OS가 어두운 모양으로 설정되면 실행됩니다.
@media (prefers-color-scheme: dark) {
:root {
--color-bg-base: #0F0F17;
--color-bg-elevated: #1A1A2E;
--color-bg-overlay: #252540;
--color-text-primary: #E8E8F0;
--color-text-secondary: #A8A8C0;
--color-text-muted: #6A6A88;
--color-border: #2E2E4A;
--color-border-strong: #4A4A6A;
--color-accent: #60A5FA;
--color-accent-hover: #93C5FD;
--color-success: #4ADE80;
--color-warning: #FCD34D;
--color-danger: #F87171;
}
}
이 접근 방식은 JavaScript 없이 작동하고 레이아웃 변경이 없으며 페이지 로드 시 사용자가 선언한 기본 설정을 즉시 존중합니다. 올바른 기준점입니다. 제한 사항은 사용자가 앱에서 이를 재정의할 수 없다는 것입니다. OS가 어두우면 사이트도 어둡고 탈출할 수 없습니다. 이것이 바로 대부분의 프로덕션 구현이 미디어 쿼리 위에 JavaScript 토글을 배치하는 이유입니다.
두 가지 접근 방식 결합
권장 패턴은 미디어 쿼리를 기본값으로 사용하고 data-theme 속성을 명시적 재정의로 사용합니다. CSS 특정 트릭을 사용하거나 규칙을 올바르게 정렬하여 이를 처리할 수 있습니다.
/* 1. Light mode default */
:root {
--color-bg-base: #FFFFFF;
/* ... */
}
/* 2. OS dark mode override (when no explicit preference set) */
@media (prefers-color-scheme: dark) {
:root:not([data-theme="light"]) {
--color-bg-base: #0F0F17;
/* ... */
}
}
/* 3. Explicit dark mode (user toggled via JS) */
[data-theme="dark"] {
--color-bg-base: #0F0F17;
/* ... */
}
미디어 쿼리의 :not([data-theme="light"]) 선택기는 사용자가 명시적으로 밝은 모드를 선택하지 않은 경우에만 OS 어두운 기본 설정이 적용됨을 의미합니다. 전환하면 명시적인 선택이 승리합니다.
JavaScript를 사용한 토글 메커니즘
잘 구현된 토글은 세 가지 작업을 수행합니다. 즉, 현재 모양을 즉시 변경하고, localStorage에서 기본 설정을 유지하며, 첫 번째 페인트 전에 페이지 로드 시 저장된 기본 설정을 읽습니다.
로드 시 읽기 기본 설정
잘못된 테마가 표시되는 것을 방지하려면 페이지가 렌더링되기 전에 이 스크립트를 <head>에서 실행해야 합니다.
<head>
<script>
(function() {
const stored = localStorage.getItem('theme');
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
const theme = stored ?? (prefersDark ? 'dark' : 'light');
document.documentElement.setAttribute('data-theme', theme);
})();
</script>
</head>
스타일이 적용되기 전에 <html>에 data-theme가 즉시 설정됩니다. 브라우저는 플래시 없이 첫 번째 페인트에서 올바른 사용자 정의 속성 값을 계산합니다.
토글 기능
function toggleTheme() {
const current = document.documentElement.getAttribute('data-theme');
const next = current === 'dark' ? 'light' : 'dark';
document.documentElement.setAttribute('data-theme', next);
localStorage.setItem('theme', next);
}
// Hook it up to a button
document.getElementById('theme-toggle').addEventListener('click', toggleTheme);
토글 버튼 상태 동기화
토글 버튼은 현재 모드를 시각적으로 반영해야 합니다. 간단한 접근 방식에서는 아이콘을 사용합니다.
<button id="theme-toggle" aria-label="Toggle dark mode">
<span class="icon-light">☀️</span>
<span class="icon-dark">🌙</span>
</button>
[data-theme="dark"] .icon-light { display: none; }
[data-theme="dark"] .icon-dark { display: inline; }
[data-theme="light"] .icon-light { display: inline; }
[data-theme="light"] .icon-dark { display: none; }
아이콘 가시성은 data-theme에 연결된 CSS 변수에 의해 제어되므로 속성이 변경될 때마다 버튼 상태가 자동으로 업데이트되며 추가 JavaScript가 필요하지 않습니다.
색상 적응 전략
다크 모드 색상을 선택하는 것은 라이트 팔레트를 뒤집는 것만큼 간단하지 않습니다. 좋은 어두운 색상 선택을 안내하는 몇 가지 원칙이 있습니다.
단순히 뒤집는 것이 아니라 대비를 줄입니다.
순수한 검정색 배경의 순수한 흰색 텍스트(#000000의 #FFFFFF)는 기술적으로 최대 대비(21:1)이지만 장시간 읽기에는 인지적으로 피곤합니다. 양 극단을 줄이세요. 본문 텍스트에는 #E8E8F0와 같은 황백색을 사용하고 페이지 배경에는 #0F0F17와 같은 매우 어두운 남색을 사용하세요. 이는 시각적 피로를 줄이면서 충분한 대비(여전히 15:1 이상)를 유지합니다.
대비 검사기를 사용하여 어두운 테마의 모든 텍스트/배경 조합이 최소한 WCAG AA(일반 텍스트의 경우 4.5:1, 큰 텍스트의 경우 3:1)를 충족하는지 확인하세요. 일반적인 실패 지점은 다음과 같습니다.
- 양식 필드의 자리 표시자 텍스트
- 비활성화된 버튼 라벨
- 보조 메타데이터 텍스트(타임스탬프, 작성자 이름)
- 라벨이 보이지 않고 아이콘만 있는 버튼
어두운 표면이 있는 계층화된 고도
조명 모드에서 고도는 일반적으로 그림자로 표현됩니다. 어두운 모드에서는 어두운 배경에서 그림자가 보이지 않게 됩니다. Material Design 3 사양에서는 가벼운 표면이 더 높게 느껴집니다라는 보다 효과적인 접근 방식을 도입했습니다. 높은 구성 요소에는 미묘하게 밝은 배경을 사용합니다.
/* Dark mode elevation scale */
--color-bg-base: #0F0F17; /* Page background */
--color-bg-elevated: #1A1A2E; /* Cards, sidebars */
--color-bg-overlay: #252540; /* Modals, dropdowns */
--color-bg-tooltip: #2E2E4A; /* Tooltips */
기본으로 #0F0F17, 카드용으로 #1A1A2E, 모달용으로 #252540 - 각 단계는 HSL 밝기 측면에서 약 8~10% 더 가벼워집니다. 이는 그림자에 의존하지 않고 명확한 시각적 계층 구조를 만듭니다.
다크 모드 색상의 채도를 약간 낮추기
채도가 높은 색상은 어두운 배경에서 거칠고 네온처럼 보입니다. 브랜드 색상을 다크 모드에 맞게 조정할 때 밝기를 높이는 동시에 채도를 10~20% 줄이세요. 생생한 #22C55E 성공 녹색 대신 #4ADE80를 선호합니다. 더 가볍고 채도가 약간 낮아 눈의 피로 없이 성공으로 읽혀집니다.
Shade Generator가 여기에 이상적입니다. 브랜드의 기본 녹색 또는 파란색을 입력하고 어두운 모드 텍스트 및 아이콘 사용을 위한 300-400 범위를 탐색하고 대화형 요소를 위한 500-600 범위를 탐색하십시오.
이미지 및 미디어
흰색 배경의 이미지는 어두운 모드에서 이상하게 보입니다. CSS가 도움이 될 수 있습니다:
/* Reduce harshness of images in dark mode */
[data-theme="dark"] img:not([src*=".svg"]) {
filter: brightness(0.9) contrast(1.05);
}
/* Or allow images to blend slightly with the background */
[data-theme="dark"] img {
mix-blend-mode: luminosity;
opacity: 0.9;
}
조정이 필요한 SVG 아이콘의 경우 currentColor를 채우기 값으로 사용하면 현재 텍스트 색상이 자동으로 채택됩니다.
.icon { color: var(--color-text-secondary); }
<svg fill="currentColor" viewBox="0 0 24 24">...</svg>
두 모드 모두 테스트
철저한 테스트를 통해 다크 모드 회귀가 프로덕션에 적용되는 것을 방지합니다.
브라우저 DevTools 에뮬레이션
Chrome과 Firefox는 모두 OS 설정을 변경하지 않고도 DevTools에서 다크 모드 에뮬레이션을 제공합니다. Chrome에서: DevTools를 열고 점 3개 메뉴를 클릭한 다음 추가 도구 → 렌더링으로 이동하여 "CSS 미디어 기능 선호 색상 구성표 에뮬레이트"를 "어둡게"로 설정합니다. 이를 통해 두 모드를 나란히 비교할 수 있습니다.
자동 대비 테스트
수동으로 부분 점검을 하면 오류가 발생하기 쉽습니다. 자동화된 대비 감사를 개발 워크플로에 통합하세요. CI의 Axe 또는 Lighthouse와 같은 도구를 사용하여 WCAG 임계값을 충족하지 못하는 새로운 색상 추가를 포착하세요. 대비 검사기를 사용하면 모든 WCAG 레벨에 대해 전경/배경 쌍을 빠르게 확인할 수 있습니다. 임의의 16진수 쌍을 붙여넣고 즉시 비율을 확인할 수 있습니다.
실제 콘텐츠로 테스트
다크 모드 버그는 사용자가 업로드한 이미지, 타사 삽입, 차트, 지도 등 동적 콘텐츠가 포함된 페이지에 자주 나타납니다. 디자인 시스템의 구성 요소 라이브러리뿐만 아니라 현실적인 콘텐츠 샘플을 테스트해 보세요.
OS 수준 테스트
DevTools 에뮬레이션을 통해 확인한 후 실제로 OS를 다크 모드로 설정하여 테스트하세요. prefers-color-scheme 미디어 쿼리는 OS 설정에 따라 실행되며 일부 브라우저는 설정이 실제인지 에뮬레이션인지에 따라 약간 다르게 동작합니다. 또한 전환을 테스트하십시오. 페이지가 열려 있는 동안 모드를 전환하고 레이아웃 변경이나 렌더링 아티팩트가 발생하지 않는지 확인하십시오.
일반적인 함정 체크리스트
- 변수 대신 구성요소 CSS에 하드코딩된 16진수 값 — 스타일시트에서 원시 16진수 코드를 검색하고 변수로 대체
- 하드코딩된
fill="#000000"가 있는 SVG 아이콘 —fill="currentColor"로 변경 data-theme를 따르지 않는 타사 구성 요소 — 범위가 지정된 CSS 레이어로 래핑color-scheme속성이 설정되지 않음 -color-scheme: light dark를:root에 추가하면 브라우저 크롬(스크롤 막대, 양식 컨트롤)도 이에 맞게 조정됩니다.<head>에서 누락된<meta name="color-scheme">— CSS가 로드되기 전에 브라우저가 올바른 배경색을 적용할 수 있도록 추가하세요.
<meta name="color-scheme" content="light dark">
:root {
color-scheme: light dark;
}
이 작은 추가로 기본 스크롤 막대, 날짜 선택기 및 기타 OS 렌더링 양식 컨트롤이 자동으로 어두운 변형으로 전환됩니다. 이는 많은 구현에서 간과되는 세부 사항입니다.
주요 내용
:root에서 모든 색상을 CSS 사용자 정의 속성으로 정의하고[data-theme="dark"]를 사용하여 어두운 모드에 대해 재정의합니다. 구성 요소 스타일은 변수만 참조하므로 팔레트가 설정되면 테마를 쉽게 전환할 수 있습니다.- OS를 어두운 화면으로 설정한 사용자의 경우
prefers-color-scheme: dark를 자동 기본값으로 사용합니다. 재정의하려는 사용자를 위해localStorage지속성을 갖춘 JavaScript 토글을 맨 위에 레이어합니다. - 잘못된 테마의 첫 페인트 플래시를 방지하려면 CSS가 로드되기 전에
<head>에서 플래시 방지 스크립트를 실행하세요. - 다크 모드 색상은 밝은 색상이 반전되지 않습니다. 대비를 극도로 줄이고, 밝은 배경을 사용하여 고양감을 전달하고, 브랜드 강조의 채도를 약간 낮추어 네온의 거친 느낌을 방지합니다.
- 대비 검사기로 모든 텍스트/배경 쌍을 확인하고 색조 생성기를 사용하여 두 테마에 대한 각 브랜드 색상의 올바른 음영을 찾습니다.
- 기본 브라우저 UI 요소(스크롤바, 입력)도 자동으로 전환되도록
color-scheme: light dark및 해당<meta>태그를 추가합니다.