pile·
프론트엔드·카카오 엔터테인먼트 FE카카오 엔터테인먼트 FE·

웹에서 다크모드 지원하기

카카오웹툰 팀이 웹에서 다크모드를 지원하면서 겪은 시행착오와 최종 채택한 방법을 정리한 글이다. CSS 미디어 쿼리 방식의 한계를 설명하고, 앱 웹뷰 환경을 포함한 실용적인 해결책을 제시한다.

핵심 포인트 - prefers-color-scheme 미디어 쿼리는 시스템 다크모드만 따라가서 앱 내 웹뷰 동기화 불가 - 구버전 Android 미지원, 사용자 직접 선택 기능 구현 어려움 등 미디어 쿼리 방식의 현실적 한계 - 추천 방법: body 태그에 .dark 클래스 부여 + JavaScript DOM 조작으로 런타임 제어 - localStorage로 사용자 선택 저장해 새로고침 후에도 유지 - 앱 웹뷰는 네이티브 앱과 postMessage/JSBridge로 다크모드 상태 동기화 - CSS 변수(--color-*)로 색상 토큰화해 .dark 클래스 하나로 전체 테마 전환

상세 정리 - prefers-color-scheme의 한계: 시스템 설정 의존, 앱 웹뷰에서 OS 설정과 앱 설정 불일치 발생 - 구버전 Android 브라우저 미지원으로 사용자 일부가 항상 라이트모드만 보는 문제 - 미디어 쿼리로는 사용자가 앱 내에서 다크/라이트 직접 선택하는 UX 구현 어렵거나 복잡 - 해결책: JavaScript로 document.body.classList.toggle('dark') 방식 채택 - CSS: :root 에 --bg, --text 등 변수 정의 → .dark 클래스 내에서 변수 재정의 - localStorage 키에 사용자 선택 저장 → 페이지 로드 시 즉시 적용해 FOUC 방지 - 앱 웹뷰 동기화: 앱이 window.postMessage로 다크모드 상태 전달 → 웹에서 클래스 적용 - 반대로 웹에서 선택 변경 시 앱에 postMessage로 통보해 네이티브 UI도 동기화 - 초기 로드 시 깜빡임(FOUC) 방지: <head> 인라인 스크립트로 클래스를 즉시 설정 - SSR 환경(Next.js)에서는 서버 렌더링 시 기본값 적용 후 클라이언트 hydration 시 로컬 설정 반영 - 다크모드 아이콘·이미지: CSS filter: invert() 또는 별도 다크 버전 에셋 사용 검토

왜 읽나: 미디어 쿼리로 다크모드를 구현했다가 앱 웹뷰 동기화 문제에 부딪힌 개발자, 또는 처음부터 제대로 된 다크모드 아키텍처를 설계하려는 개발자에게 실전 경험 기반의 해답을 제공한다.

카카오 엔터테인먼트 FE
카카오 엔터테인먼트 FE 블로그
원문은 여기서 이어서 읽을 수 있어요
원문 읽기
읽음 (0)

이 글과 비슷한

  1. 프론트엔드·여기어때 (GC컴퍼니)여기어때 (GC컴퍼니)·

    항공 프론트엔드 구축기 (7/10): 창구를 하나만 두었습니다

    여기어때 항공 서비스 프론트엔드가 웹과 앱 웹뷰 두 환경에서 동일한 함수 호출로 동작하는 앱 브릿지 추상화 레이어를 설계한 과정을 다룬다. iOS·안드로이드 규약 차이와 "웹에 존재하지 않는 브릿지를 어떻게 호출하나"라는 문제를 단일 추상화 층으로 해결한 구현 사례다.

    요약 이어보기
    #react#typescript#webview+2