다크모드는 '있으면 좋은 기능'이 아니다
솔직히 말하면, 다크모드 토글 버튼 하나 만들면서 뿌듯해했던 때가 있었다. 그런데 Figma 시안에서 보던 그 깔끔한 다크 테마가 실제 브라우저에서 열리는 순간—border는 뭔가 이상하고, shadow는 너무 진하고, 텍스트 대비는 WCAG AA도 겨우 통과하는 수준이었다. 다크모드는 단순히 색 반전이 아니다. 그 순간 깨달았다. 이건 배경색 몇 개 바꾸는 작업이 아니라, 앱 전체 디자인을 두 번 만드는 일이다.
"MVP에서 빼라"—이 말이 맞는 이유
dev.to에 올라온 Stop Building Settings Pages 아티클은 다크모드를 MVP에서 잘라내야 할 1순위 기능으로 꼽는다. 근거가 꽤 설득력 있다. "첫 100명의 사용자는 눈이 조금 따가운 것보다, 앱이 자기 시간을 아껴주는지에만 관심 있다." Linear도 라이트 모드 없이 수년간 운영했다. 다크모드를 제대로 구현하려면 모든 border, shadow, text contrast를 두 가지 상태에서 테스트해야 한다. CSS 작업량이 두 배다. 스크린샷도 두 배, QA도 두 배, 디자이너와의 피드백 루프도 두 배.
사용자 입장에서는 앱이 문제를 해결해주는지가 먼저다. 다크모드 여부는 그다음이다. 모멘텀이 부족한 초기 단계에서 이 비용을 지불하는 건 냉정히 보면 기술적 자기만족에 가깝다.
그런데 "나중에"가 더 무섭다
문제는 나중이다. 다크모드를 처음부터 설계에 녹이지 않으면, 나중에 붙이는 순간 레거시 코드와의 전쟁이 시작된다. 하드코딩된 hex 값들, color: black 같은 인라인 스타일, 의미 없는 클래스명들—이게 전부 발목을 잡는다.
대학생 과제 관리 앱 CHECKTASK 회고에서 프론트엔드 리드가 공유한 접근 방식이 여기서 인상적이다. PandaCSS의 semanticTokens를 이용해 bg, fg.default 같은 시맨틱 토큰으로 색상을 관리하면, 컴포넌트에서는 bg: 'bg'라고만 써도 라이트/다크가 자동으로 전환된다. 별도 JavaScript 로직 없이, CSS만으로. 여기에 Next.js App Router에서 서버 컴포넌트가 쿠키를 읽어 <html data-theme="..."> 초기값을 세팅하면 페이지 로드 시 테마가 깜빡이는 FOUC(Flash of Unstyled Content) 문제도 잡힌다.
이게 바로 핵심이다. 다크모드를 나중에 붙이는 게 아니라, 처음부터 토큰 시스템으로 설계하면 추가 공수가 거의 없다.
다크모드보다 더 나은 선택지: 사용자에게 권한을 주자
velog에 올라온 다크모드를 사용자에게 권한을 넘겨주자 아티클은 흥미로운 관점을 제시한다. 라이트/다크라는 이분법 자체를 의심하는 것이다. 시각적 접근성은 이분법적이지 않다. 어떤 사람은 밤에 화면이 조금만 더 어두워야 하고, 어떤 사람은 더 높은 명도 대비가 필요하다.
이 글에서 제안하는 방법은 backdrop-filter 오버레이다. 전체 화면을 덮는 투명한 <div>에 backdrop-filter를 적용하고, pointer-events: none으로 클릭을 통과시킨다. 사용자는 밝기, 대비, 채도, 색조를 슬라이더로 직접 조절할 수 있고, 설정은 localStorage에 저장된다.
기술적으로 흥미로운 포인트가 몇 가지 있다:
- 루트 요소에
filter직접 적용을 피하는 이유:position: fixed오작동, 루트 배경색 미포함, 뷰포트 밖 콘텐츠까지 렌더링하는 성능 문제가 생긴다. 오버레이 방식은 이걸 전부 회피한다. backdrop-filtervsfilter:filter는 요소 자체와 자식을 변형하지만,backdrop-filter는 요소 뒤쪽 배경만 변형한다. 필터 렌즈처럼 작동해서 기존 DOM 구조를 건드리지 않는다.- 성능: 요즘
backdrop-filter는 GPU에서 렌더링되어 60FPS 스크롤이 가능하다. 기본값일 때는 오버레이를display: none으로 숨겨 오버헤드를 없앤다. - 접근성: 오버레이
div에aria-hidden="true"처리, 컨트롤 패널에role="region",aria-label추가. 컨트롤 패널은z-index로 오버레이 위에 띄워서 사용자가 실수로 UI를 안 보이게 해도 항상 복원할 수 있게 한다.
다만 이 아티클 자체도 솔직한 한계를 인정한다. 선택지가 너무 많으면 오히려 피로감이 생긴다. 실제로 CSS 필터만으로는 원하는 색감을 완벽하게 재현하기 어려운 경우도 있다. 그래서 결국 "잘 만든 라이트모드 + 잘 만든 다크모드"가 대부분의 사용자에게 더 나은 경험이라는 결론으로 귀결된다.
그래서, 언제 어떻게 만들어야 하나
정리하면 이렇다:
MVP 단계 (첫 출시)
- 다크모드 토글 빼도 된다. 대신 라이트모드를 제대로 만들어라. 텍스트 대비 4.5:1 이상(WCAG AA), 포커스 링 명확히, 고대비 색상 조합.
- 단, 처음부터 디자인 토큰으로 색상 관리하라. color: #1a1a1a 하드코딩 금지. 나중에 다크모드 붙일 때 이 차이가 3일 작업이냐 3주 작업이냐를 가른다.
프로덕트-마켓 핏 이후
- prefers-color-scheme 미디어 쿼리로 시스템 설정 먼저 따라가는 방식으로 시작하라. 사용자가 OS에서 다크모드 설정했으면 그걸 존중하는 게 기본이다.
- localStorage나 쿠키로 사용자 선택을 저장하고, 서버 컴포넌트(Next.js App Router라면)에서 초기값 세팅해 FOUC를 잡아라.
- 접근성이 중요한 서비스라면 backdrop-filter 기반 커스터마이징 컨트롤도 검토할 만하다. 특히 의료, 교육, 공공 서비스 도메인.
결국 다크모드는 '기능'이 아니라 '설계 철학'이다
"Figma에서 볼 때는 괜찮았는데, 실제로 구현하면" 다른 얘기가 되는 게 다크모드다. 토큰 없이 만들면 나중에 무너지고, 접근성 없이 만들면 특정 사용자를 배제한다. MVP에서 빼는 건 맞는 판단일 수 있다. 하지만 빼되, 나중에 제대로 붙일 수 있는 구조는 처음부터 만들어야 한다. 그게 지금 당장 다크모드 토글 하나 만드는 것보다 훨씬 가치 있는 작업이다.