다크 모드는 6단계다, 스위치 하나가 아니다
CSS 구현 깊이에 따라 사용자 경험이 달라지는 이유
왜 이 한 편인가
다크 모드 논의는 대개 "지원 여부"에서 멈추지만, cssence.com이 정리한 6단계 구조는 3단계(CSS 변수 재정의)와 5단계(SVG·이미지 분기) 사이의 간극이 실제 UX 결함으로 이어진다는 점을 수치 없이도 명확히 드러낸다. 데이터 시각화가 핵심인 SaaS 제품에서 "지원한다"고 선언한 다크 모드가 사실상 2단계에 머물러 있는 사례가 반복되는 시점에, 구현 깊이를 진단하는 체크리스트 각도로 골랐다.
다크 모드를 "켜는 것"과 "구현하는 것"은 다르다
프로덕트 팀이 "다크 모드 지원합니다"라고 선언할 때, 그 말이 가리키는 구현 깊이는 팀마다 다르다. HTML 메타 선언 한 줄로 끝낸 팀도, 자바스크립트 상태 관리까지 완성한 팀도 같은 말을 쓴다. 문제는 그 간극이 사용자 경험에 직접 드러난다는 점이다.
cssence.com이 정리한 6단계 구조는 이 간극을 단계별로 진단한다. 1단계는 <meta name="color-scheme" content="dark light"> 선언으로, 브라우저가 기본 폼 요소·스크롤바·배경색을 OS 설정에 맞게 조정한다. 비용은 없지만 커스텀 CSS 색상은 그대로 남는다. 2단계는 @media (prefers-color-scheme: dark) 블록으로 배경과 텍스트를 오버라이드한다. 컬러 변수가 수십 개인 프로덕트에서는 유지보수가 불가능해지는 지점이다. 3단계는 CSS 커스텀 프로퍼티 레이어로, :root에 라이트 팔레트를 선언하고 [data-theme="dark"] 셀렉터에서 같은 변수 이름으로 다크 값을 재정의한다. 대부분의 디자인 시스템이 이 단계를 목표로 삼는다. 4단계는 light-dark() 함수와 color-scheme 속성을 활용해 시스템 UI와 커스텀 UI 사이의 색상 불일치를 차단한다. 5단계부터는 CSS 변수로 제어할 수 없는 영역, 즉 <img> 사진·인라인 SVG·<canvas> 차트를 filter: invert(1) hue-rotate(180deg) 또는 <picture> 태그의 미디어 조건으로 분기한다. 6단계는 localStorage에 테마 선택을 기록하고 페이지 로드 초기에 <body>에 클래스를 적용한다. 이 단계가 없으면 사용자가 선택한 테마는 새로고침마다 시스템 설정으로 덮인다.
단계를 건너뛰면 구체적인 결함이 남는다. 3단계에서 멈추면 CSS 변수로 배경과 텍스트를 바꿨더라도 차트와 SVG 아이콘은 흰 박스로 떠 있다. 6단계를 생략하면 사용자가 직접 선택한 테마가 새로고침 한 번에 사라진다. "지원한다"는 선언이 완성된 경험을 보장하지 않는다(cssence.com).