들어가며: 다크모드 토글, 생각보다 복잡한 문제

웹사이트 헤더에 다크모드 토글 하나 넣는 건 쉬워 보입니다. 그런데 막상 구현하려면 고민이 시작되죠. 라이트/다크/시스템 세 가지 옵션을 다 보여줘야 할까요? 아니면 두 개만 있으면 충분할까요?

최근 CSS 커뮤니티에서 이 주제로 뜨거운 논쟁이 있었습니다. 결론부터 말씀드리면, 상시 노출되는 토글에서는 2단계면 충분하다는 쪽으로 의견이 모이고 있어요. 단순히 "간단해서"가 아니라, 사용자의 인지 부하와 실제 사용 맥락을 고려한 논리적인 이유가 있습니다.

이 글에서는 그 논쟁의 핵심을 정리하고, 실무에서 바로 적용할 수 있는 구현 패턴까지 다뤄보겠습니다. 근거자료는 CSS-Tricks의 원문을 참고하시면 됩니다.

UI designer comparing two-state and three-state dark mode toggle designs on a whiteboard Programming Illustration

핵심 논리: 왜 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' 상태를 저장할 필요가 없어요.

Developer implementing dark mode toggle with localStorage in a code editor Coding Session Visual

주의사항 및 심화 팁

1. "시스템으로 되돌리기"는 어떻게?

여기서 흔히 나오는 질문이 있습니다. "다크로 바꿨다가 다시 라이트로 돌렸는데, 이게 시스템 설정 복귀인가요, 아니면 라이트 고정인가요?"

결론은 그냥 라이트 고정으로 처리해도 된다는 겁니다. 왜냐하면:

  • 테마 토글은 사용자의 주요 목적과 무관한 부가 기능입니다.
  • 시스템 설정과 오버라이드가 어긋나는 시점은 OS 테마가 바뀔 때인데, 그때 한 번 더 클릭하면 그만이에요.
  • 이 정도 마찰은 사용자가 인지하지도 못합니다.

2. 3단계 토글이 정당화되는 예외 상황

그렇다고 3단계가 항상 나쁜 건 아닙니다. 다음 두 경우엔 3단계가 오히려 적절합니다.

  • 별도 설정 패널에 위치한 경우 (헤더의 상시 토글이 아니라, 설정 페이지 안)
  • 시스템 설정에 따라 다른 컬러 스킴을 제공하는 경우 (예: 라이트/다크/고대비 등 3개 이상의 스킴)

즉, "상시 노출되는 헤더 토글"이라는 맥락이 2단계를 정당화하는 핵심 조건입니다.

3. 아예 토글을 없애는 선택지

가장 과격한(그리고 어쩌면 가장 좋은) 해법은 토글을 아예 두지 않는 것입니다. prefers-color-scheme만 따르게 하면 UI도 줄고, 사용자도 고민할 게 없어집니다. 다만 사용자층이 다양한 서비스라면 오버라이드 수단은 필요하겠죠.

💡 실무 팁: FOUC(Flash of Unstyled Content)를 막으려면 <head>에 인라인 스크립트로 테마를 즉시 적용하세요. React라면 next-themes 같은 라이브러리가 이 문제를 잘 처리해줍니다.

Laptop screen showing a website header with a simple light/dark toggle switch Developer Related Image

결론: 적을수록 좋다, 단 맥락을 이해하고

정리하면 이렇습니다.

  1. 상시 노출 토글에는 2단계면 충분합니다. 'System' 옵션은 인지 부하만 늘립니다.
  2. localStorage의 존재 여부가 곧 오버라이드 상태이므로, 별도 상태 관리가 불필요합니다.
  3. 설정 패널이나 3개 이상의 스킴을 제공할 때만 3단계를 고려하세요.
  4. 궁극적으로는 토글 없는 디자인도 진지하게 검토해볼 만합니다.

한국 개발 생태계에서의 적용 맥락

국내 SI/외주 환경에서는 발주처 요구로 "3개 다 넣어달라"는 요청이 종종 옵니다. 이럴 때 이 글의 논리를 근거로 "2단계로도 충분하고, 사용자 이탈을 줄일 수 있다"고 설득해보세요. 특히 관리자 페이지처럼 설정 항목이 많은 화면과, 사용자 대면 헤더는 분리해서 접근하는 게 좋습니다.

다음 단계 학습 방향

  • prefers-color-scheme 미디어 쿼리와 color-scheme CSS 속성의 차이
  • CSS 변수를 활용한 테마 시스템 설계 (다음 글에서 다룰 StyleX 가이드도 큰 그림에서 연결됩니다)
  • FOUC 방지를 위한 SSR 환경 테마 주입 전략

함께 보면 좋은 글

본 콘텐츠는 신뢰할 수 있는 출처를 바탕으로 AI 도구를 활용하여 초안이 작성되었으며, 편집자의 검토를 거쳐 발행되었습니다. 전문가의 조언을 대체하지 않습니다.