다크 모드는 토글 하나가 아니야. 현재 모드, 선택 저장, 첫 렌더링 전 적용을 함께 풀어야 화면이 깜빡이지 않아.
세 가지 상태를 함께 다뤄
앱은 밝은 모드, 어두운 모드, 시스템 설정 따르기 중 하나를 선택해야 해. 사용자의 선택은 localStorage에 저장해 새로고침 뒤에도 유지하고, React가 렌더링되기 전에 html의 theme 속성에 반영해야 잘못된 색이 잠깐 보이지 않아.
React보다 먼저 실행하는 스크립트
index.html의 짧은 inline script에서 저장된 값을 읽고, 값이 없으면 prefers-color-scheme을 확인해. Vite 번들이 실행되기 전에 data-theme을 설정하면 브라우저가 처음부터 올바른 색으로 화면을 그려.
React 훅은 사용자 변경을 맡아
useTheme은 현재 속성을 읽고 모드를 바꾸는 함수를 제공해. 사용자가 토글하면 DOM 속성과 localStorage를 함께 갱신해. 초기 부팅은 inline script가, 이후 상호작용은 훅이 맡는 구조야.
시스템 모드도 변경될 수 있어
사용자가 직접 모드를 고르지 않았다면 matchMedia의 change 이벤트를 구독해 운영체제 설정을 따라가. 사용자가 명시적으로 고른 순간부터는 저장된 선택이 시스템 신호보다 우선해.
깜빡임을 직접 테스트해
개발 서버가 빠르면 잘못된 테마가 보이지 않을 수 있어. Network 속도를 낮추고 JavaScript 실행을 늦춘 뒤 새로고침해도 첫 paint부터 올바른 색인지 확인해. 저장된 값이 없을 때와 light·dark가 각각 저장됐을 때를 모두 시험해.
System 모드를 구독했다면 cleanup에서 media query listener를 제거해. 사용자가 직접 모드를 고른 뒤에는 시스템 변경이 저장된 선택을 덮지 않도록 분기를 분명히 둬.