다크 모드
getInitialTheme()이 localStorage에 저장된 값을 먼저 확인하고, 없으면 prefers-color-scheme: dark 미디어쿼리로 시스템 설정을 읽어옵니다.
선택된 테마는 document.documentElement.dataset.theme에 반영되며, base.css의 [data-theme="dark"] 선택자가 같은 이름의 CSS 변수를 다른 값으로 덮어써서 색상 전체가 한 번에 바뀝니다.
:root {
--color-bg: #f8fafc;
--color-text: #182230;
}
[data-theme="dark"] {
--color-bg: #0f172a;
--color-text: #f8fafc;
}토글 시마다 localStorage.setItem('theme', ...)으로 값을 저장해 새로고침 후에도 유지됩니다.
왜 쿠키가 아니라 localStorage일까
브라우저에 값을 저장하는 방법은 쿠키, localStorage, sessionStorage 등 여러 가지입니다. 테마 값은 다음 이유로 localStorage가 잘 맞습니다.
| 쿠키 | localStorage | sessionStorage | |
|---|---|---|---|
| 서버로 자동 전송 | 요청마다 함께 전송됨 | 전송 안 됨 | 전송 안 됨 |
| 유지 기간 | 만료 시각까지 | 직접 지울 때까지 | 탭을 닫을 때까지 |
| 용량 | 약 4KB | 보통 5MB 안팎 | 보통 5MB 안팎 |
| 이 프로젝트에 맞는가 | 서버가 테마를 알 필요가 없어 낭비 | 새로고침·재방문 후에도 유지되어 적합 | 탭을 닫으면 사라져 부적합 |
테마는 화면을 그리는 브라우저만 알면 되는 값이라, 요청마다 서버로 따라가는 쿠키에 담을 이유가 없습니다. 반대로 서버가 처음부터 다크 모드 HTML을 만들어 보내야 하는 구조(서버 렌더링)라면 쿠키에 저장하는 편이 맞습니다. 저장소별 차이는 쿠키와 세션 글에서 자세히 다룹니다.
새로고침할 때 화면이 잠깐 번쩍이는 문제
다크 모드를 켜 둔 상태에서 새로고침하면, 아주 잠깐 밝은 화면이 보였다가 어두워지는 경우가 있습니다. 브라우저가 화면을 그리는 순서 때문입니다.
HTML 파싱 → CSS 적용 → 첫 화면 그리기(밝은 테마) → main.js 실행 → data-theme="dark" → 다시 그리기
↑ 이 순간이 번쩍임테마를 정하는 JS가 첫 화면이 그려진 뒤에 실행되면 그 사이에 기본(밝은) 색이 보이는 것입니다. 해결 방법은 테마를 정하는 짧은 코드를 <head> 안에서, CSS가 적용되기 전에 먼저 실행하는 것입니다.
<head>
<script>
const saved = localStorage.getItem('theme');
const dark = saved ? saved === 'dark' : matchMedia('(prefers-color-scheme: dark)').matches;
document.documentElement.dataset.theme = dark ? 'dark' : 'light';
</script>
<link rel="stylesheet" href="base.css" />
</head>이 사이트(1mo.dev)를 만든 VitePress도 같은 방식으로 <head>에 작은 스크립트를 넣어 번쩍임을 막습니다. 브라우저가 HTML을 읽고 화면을 그리는 순서는 브라우저 동작 원리 글에서 자세히 다룹니다.
정리
| 순서 | 하는 일 |
|---|---|
| 1 | localStorage에 저장된 테마가 있으면 사용 |
| 2 | 없으면 OS 설정(prefers-color-scheme)을 따름 |
| 3 | <html data-theme>를 바꿔 CSS 변수를 통째로 교체 |
| 4 | 버튼을 누를 때마다 localStorage에 저장 |
- 테마는 서버가 알 필요 없는 값이라 쿠키보다
localStorage가 적합 - 새로고침 번쩍임을 없애려면 테마 결정 코드를
<head>에서 화면을 그리기 전에 실행
출처: Codyssey-B1/B1-1
