웹에서 다크모드 지원하기
어두운 바탕에 밝은 글씨를 보여주는 다크모드는 눈의 피로감을 줄여주고, OLED 디스플레이에서는 배터리 절약 효과도 있습니다. 글자가 많거나 콘텐츠 소비 시간이 긴 서비스일수록 다크모드 지원이 중요해집니다.
다만 밝은 환경에서는 오히려 가독성이 떨어질 수 있으므로, 일반적으로 라이트/다크 모드를 선택할 수 있도록 제공하는 것이 좋습니다.
이 글에서는 웹에서 다크모드를 구현하는 대표적인 방법들과 각각의 특징을 소개합니다.
1. CSS prefers-color-scheme 미디어 쿼리
사용자 시스템의 컬러 모드 설정을 감지해 자동으로 스타일을 적용하는 방식입니다.
.example {
background: white;
color: black;
}
@media (prefers-color-scheme: dark) {
.example {
background: black;
color: white;
}
}
장점
- 구현이 간단하고 JavaScript 없이 동작합니다
- 시스템 설정과 자동으로 동기화됩니다
- 현재 대부분의 브라우저와 OS에서 지원합니다
단점
- 사용자가 웹사이트 내에서 별도로 모드를 선택할 수 없습니다
- 항상 시스템 설정을 따라가므로 자체적인 컬러모드 정책을 가져가기 어렵습니다
- 웹뷰 환경에서 앱의 다크모드 설정을 따라야 하는 경우 대응이 어렵습니다
적합한 경우: 단순한 웹사이트이거나, 시스템 설정을 그대로 따라가는 것이 자연스러운 서비스
2. body 태그에 클래스로 컬러모드 부여하기
HTML body 태그에 클래스를 추가해 현재 컬러모드를 관리하는 방식입니다.
body {
background: white;
color: black;
}
body.dark {
background: black;
color: white;
}
<!-- 다크모드 -->
<body class="dark">
...
</body>
JavaScript로 DOM을 조작할 수 있으므로 다양한 요구사항에 대응할 수 있습니다.
// localStorage에 저장된 사용자 선택값 사용
const colorMode = window.localStorage.getItem('color_mode');
if (colorMode === 'dark') {
document.body.classList.add('dark');
}
// 시스템 설정을 초기값으로 사용
if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
document.body.classList.add('dark');
}
// 시스템 설정 변경 감지
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
mediaQuery.addEventListener('change', (e) => {
document.body.classList.toggle('dark', e.matches);
});
// 웹뷰에서 앱의 컬러모드를 전달받아 사용
const isDarkMode = navigator.userAgent.includes('{isDark property}');
if (isDarkMode) {
document.body.classList.add('dark');
}
장점
- 사용자 선택, 시스템 설정, 앱 설정 등 다양한 소스를 유연하게 활용할 수 있습니다
- 토글 스위치 등 UI를 통한 수동 전환이 가능합니다
- localStorage 등을 활용해 사용자 선택을 저장할 수 있습니다
단점
- JavaScript에 의존하므로 초기 렌더링 시 깜빡임(flicker) 문제가 발생할 수 있습니다
- SSR 환경에서 추가적인 고려가 필요합니다
적합한 경우: 사용자에게 컬러모드 선택권을 제공하거나, 웹뷰 등 다양한 환경에서 동작해야 하는 서비스
구현 시 고려사항
1. SSR 환경에서의 Flicker 방지
CSR에서 컬러모드를 판단하면, 서버에서 받은 HTML이 먼저 렌더링된 후 JavaScript가 실행됩니다. 이 때문에 다크모드 사용자가 잠시 라이트모드 화면을 보게 되는 깜빡임(flicker) 현상이 발생할 수 있습니다.
이를 방지하려면 render-blocking 특성을 활용합니다. <body> 태그 직후에 인라인 스크립트를 배치하면 나머지 콘텐츠가 렌더링되기 전에 컬러모드를 결정할 수 있습니다.
<body>
<script>
// 콘텐츠 렌더링 전에 실행됨
const saved = localStorage.getItem('color_mode');
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
if (saved === 'dark' || (!saved && prefersDark)) {
document.body.classList.add('dark');
}
</script>
<!-- 웹 콘텐츠 -->
<div id="app">...</div>
</body>
2. CSS Variables로 색상 관리하기
컬러모드마다 CSS를 두 벌씩 작성하는 것은 번거롭습니다. CSS Variables를 활용하면 색상 정의를 한 곳에서 관리할 수 있습니다.
body {
--bg-color: white;
--text-color: black;
--border-color: #e0e0e0;
}
body.dark {
--bg-color: #1a1a1a;
--text-color: #f0f0f0;
--border-color: #333;
}
/* 컴포넌트에서는 변수만 참조 */
.card {
background-color: var(--bg-color);
color: var(--text-color);
border: 1px solid var(--border-color);
}
나중에 특정 색상값을 변경해야 할 때도 body에 정의된 변수만 수정하면 됩니다.
3. color-scheme 속성 활용
브라우저 기본 UI 요소(스크롤바, 폼 컨트롤 등)의 색상도 다크모드에 맞추려면 color-scheme 속성을 함께 사용하는 것이 좋습니다.
:root {
color-scheme: light dark;
}
body.dark {
color-scheme: dark;
}
4. 이미지 대응
배경이 바뀌면서 기존 이미지가 어색해 보일 수 있습니다. 로고나 아이콘 등은 다크모드용 버전을 별도로 준비하거나, <picture> 태그와 미디어 쿼리를 활용할 수 있습니다.
<picture>
<source srcset="logo-dark.png" media="(prefers-color-scheme: dark)">
<img src="logo-light.png" alt="Logo">
</picture>
클래스 기반으로 관리한다면 CSS로 처리할 수도 있습니다.
.logo {
background-image: url('logo-light.png');
}
body.dark .logo {
background-image: url('logo-dark.png');
}
맺음말
다크모드 구현 방식은 서비스의 특성에 따라 선택하면 됩니다. 시스템 설정만 따라가도 충분하다면 prefers-color-scheme만으로도 간단하게 구현할 수 있고, 더 세밀한 제어가 필요하다면 클래스 기반 방식을 사용하면 됩니다.
실제로 적용할 때는 SSR 환경에서의 flicker 방지, 이미지 대응, 접근성 고려 등 추가적인 작업이 필요할 수 있습니다. 하지만 이런 과정을 거쳐 다크모드를 잘 지원한다면 사용자 경험을 한층 개선할 수 있습니다.