들어가며: 다크모드 토글, 생각보다 복잡한 문제
웹사이트 헤더에 다크모드 토글 하나 넣는 건 쉬워 보입니다. 그런데 막상 구현하려면 고민이 시작되죠. 라이트/다크/시스템 세 가지 옵션을 다 보여줘야 할까요? 아니면 두 개만 있으면 충분할까요?
최근 CSS 커뮤니티에서 이 주제로 뜨거운 논쟁이 있었습니다. 결론부터 말씀드리면, 상시 노출되는 토글에서는 2단계면 충분하다는 쪽으로 의견이 모이고 있어요. 단순히 "간단해서"가 아니라, 사용자의 인지 부하와 실제 사용 맥락을 고려한 논리적인 이유가 있습니다.
이 글에서는 그 논쟁의 핵심을 정리하고, 실무에서 바로 적용할 수 있는 구현 패턴까지 다뤄보겠습니다. 근거자료는 CSS-Tricks의 원문을 참고하시면 됩니다.

핵심 논리: 왜 3단계 토글이 문제인가
1. 2단계 토글로 3가지 상태를 모두 표현할 수 있다
가장 강력한 주장은 이겁니다. **"시스템 설정을 존중하되, 필요할 때만 오버라이드"**라는 패턴이면 굳이 'System' 버튼을 노출할 필요가 없다는 거죠.
- 사용자가 아무 설정도 안 했을 때 → 시스템 설정을 따름 (localStorage 비어있음)
- 사용자가 토글을 누르면 → 그 순간부터 명시적 오버라이드 (localStorage에 저장)
즉, "선호 없음"이라는 상태 자체를 UI로 표현할 필요가 없다는 관점입니다.
2. 현재 상태에 따라 반대 옵션만 보여주면 된다
더 나아가, 현재 어떤 테마가 활성화됐는지 알면 토글 자체도 하나만 보여줄 수 있습니다.
- 기본이 라이트 → "다크로 전환" 버튼만 노출
- 기본이 다크 → "라이트로 전환" 버튼만 노출
// 시스템 테마 감지 + localStorage 오버라이드 패턴
const getPreferredTheme = () => {
// 1. 사용자가 명시적으로 설정한 값이 있으면 우선
const stored = localStorage.getItem('theme');
if (stored) return stored;
// 2. 없으면 시스템 설정 따름
return window.matchMedia('(prefers-color-scheme: dark)').matches
? 'dark'
: 'light';
};
const applyTheme = (theme) => {
document.documentElement.setAttribute('data-theme', theme);
};
// 토글: 현재 테마의 반대로 전환하고 저장
const toggleTheme = () => {
const current = getPreferredTheme();
const next = current === 'dark' ? 'light' : 'dark';
localStorage.setItem('theme', next);
applyTheme(next);
};
// 초기 적용
applyTheme(getPreferredTheme());
// 시스템 설정 변경 감지 (오버라이드 없을 때만 반영)
window.matchMedia('(prefers-color-scheme: dark)')
.addEventListener('change', (e) => {
if (!localStorage.getItem('theme')) {
applyTheme(e.matches ? 'dark' : 'light');
}
});
이 코드의 핵심은 **localStorage에 값이 있는지 여부가 곧 "오버라이드 여부"**라는 점입니다. 별도의 'System' 상태를 저장할 필요가 없어요.

주의사항 및 심화 팁
1. "시스템으로 되돌리기"는 어떻게?
여기서 흔히 나오는 질문이 있습니다. "다크로 바꿨다가 다시 라이트로 돌렸는데, 이게 시스템 설정 복귀인가요, 아니면 라이트 고정인가요?"
결론은 그냥 라이트 고정으로 처리해도 된다는 겁니다. 왜냐하면:
- 테마 토글은 사용자의 주요 목적과 무관한 부가 기능입니다.
- 시스템 설정과 오버라이드가 어긋나는 시점은 OS 테마가 바뀔 때인데, 그때 한 번 더 클릭하면 그만이에요.
- 이 정도 마찰은 사용자가 인지하지도 못합니다.
2. 3단계 토글이 정당화되는 예외 상황
그렇다고 3단계가 항상 나쁜 건 아닙니다. 다음 두 경우엔 3단계가 오히려 적절합니다.
- 별도 설정 패널에 위치한 경우 (헤더의 상시 토글이 아니라, 설정 페이지 안)
- 시스템 설정에 따라 다른 컬러 스킴을 제공하는 경우 (예: 라이트/다크/고대비 등 3개 이상의 스킴)
즉, "상시 노출되는 헤더 토글"이라는 맥락이 2단계를 정당화하는 핵심 조건입니다.
3. 아예 토글을 없애는 선택지
가장 과격한(그리고 어쩌면 가장 좋은) 해법은 토글을 아예 두지 않는 것입니다. prefers-color-scheme만 따르게 하면 UI도 줄고, 사용자도 고민할 게 없어집니다. 다만 사용자층이 다양한 서비스라면 오버라이드 수단은 필요하겠죠.
💡 실무 팁: FOUC(Flash of Unstyled Content)를 막으려면
<head>에 인라인 스크립트로 테마를 즉시 적용하세요. React라면next-themes같은 라이브러리가 이 문제를 잘 처리해줍니다.

결론: 적을수록 좋다, 단 맥락을 이해하고
정리하면 이렇습니다.
- 상시 노출 토글에는 2단계면 충분합니다. 'System' 옵션은 인지 부하만 늘립니다.
- localStorage의 존재 여부가 곧 오버라이드 상태이므로, 별도 상태 관리가 불필요합니다.
- 설정 패널이나 3개 이상의 스킴을 제공할 때만 3단계를 고려하세요.
- 궁극적으로는 토글 없는 디자인도 진지하게 검토해볼 만합니다.
한국 개발 생태계에서의 적용 맥락
국내 SI/외주 환경에서는 발주처 요구로 "3개 다 넣어달라"는 요청이 종종 옵니다. 이럴 때 이 글의 논리를 근거로 "2단계로도 충분하고, 사용자 이탈을 줄일 수 있다"고 설득해보세요. 특히 관리자 페이지처럼 설정 항목이 많은 화면과, 사용자 대면 헤더는 분리해서 접근하는 게 좋습니다.
다음 단계 학습 방향
prefers-color-scheme미디어 쿼리와color-schemeCSS 속성의 차이- CSS 변수를 활용한 테마 시스템 설계 (다음 글에서 다룰 StyleX 가이드도 큰 그림에서 연결됩니다)
- FOUC 방지를 위한 SSR 환경 테마 주입 전략