다크모드, MVP에서 빼야 할까? 제대로 만드는 법

다크모드, MVP에서 빼야 할까? 제대로 만드는 법

"나중에 넣자"고 미루다 결국 대충 박아버린 다크모드—언제 넣고, 어떻게 제대로 만드는지 실무 관점에서 짚는다

다크모드 MVP 스코핑 디자인 토큰 backdrop-filter FOUC 시각적 접근성 WCAG semanticTokens
광고

다크모드는 '있으면 좋은 기능'이 아니다

솔직히 말하면, 다크모드 토글 버튼 하나 만들면서 뿌듯해했던 때가 있었다. 그런데 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-filter vs filter: filter는 요소 자체와 자식을 변형하지만, backdrop-filter는 요소 뒤쪽 배경만 변형한다. 필터 렌즈처럼 작동해서 기존 DOM 구조를 건드리지 않는다.
  • 성능: 요즘 backdrop-filter는 GPU에서 렌더링되어 60FPS 스크롤이 가능하다. 기본값일 때는 오버레이를 display: none으로 숨겨 오버헤드를 없앤다.
  • 접근성: 오버레이 divaria-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에서 빼는 건 맞는 판단일 수 있다. 하지만 빼되, 나중에 제대로 붙일 수 있는 구조는 처음부터 만들어야 한다. 그게 지금 당장 다크모드 토글 하나 만드는 것보다 훨씬 가치 있는 작업이다.

출처

더 많은 AI 트렌드를 Seedora 앱에서 확인하세요