본문으로 건너뛰기

변수만 바꾸는 다크 모드

HTML·CSS 목차움직임과 다크 모드레슨 89 / 93

목차

다크 모드는 사용자가 어두운 화면을 고른 기기에서 페이지를 어두운 색으로 보이게 만드는 일입니다. 미디어 쿼리 조건 prefers-color-scheme이 dark일 때 :root의 색 변수 값만 바꾸고, color-scheme: light dark⁠로 브라우저가 그리는 입력 칸 모양도 맞춥니다.

밝은 페이지의 바탕만 어둡게 바꾸면 짙은 글자가 바탕에 묻힙니다. 카드 · 입력 칸 · 선 · 링크도 함께 바꿔야 하는데, 규칙마다 어두운 색을 새로 적으면 고칠 곳이 늘고 한 곳만 놓쳐도 그 자리가 읽히지 않습니다. 11단원에서 레시피 노트의 색을 역할 이름의 변수 여섯으로 모았습니다. 이 레슨은 그 여섯의 값만 바꿔 다크 모드를 만들고, 변수로 바꿀 수 없는 브라우저의 칸 모양을 맞추는 한 줄을 봅니다.

그림 준비 중

1어두운 화면 조건에서 변수 값만 바꾼다

밝은 값 둘을 :root에 두고 body가 꺼내 씁니다.

<p>김치볶음밥</p>
:root {
  --bg: #f5f5f7;
  --text: #1d1d1f;
}

body {
  color: var(--text);
  background-color: var(--bg);
}
결과 화면
잰 값개발자 도구에서 본 값
  • body의 background-colorrgb(245, 245, 247)
  • p의 글자색rgb(29, 29, 31)

어두운 화면 조건 블록에서 같은 두 변수에 어두운 값을 줍니다. 아래 잰 값은 어두운 화면으로 설정한 브라우저에서 잰 값입니다. 결과 화면은 어두운 쪽을 그리지 않을 수 있습니다 — 어두운 쪽은 위 실험대와 잰 값으로 봅니다.

@media (
  prefers-color-scheme: dark
) {
  :root {
    --bg: #121820;
    --text: #f1f4f8;
  }
}
결과 화면
잰 값개발자 도구에서 본 값
  • body의 background-colorrgb(18, 24, 32)
  • p의 글자색rgb(241, 244, 248)

body 규칙은 한 글자도 바꾸지 않았는데 바탕과 글자가 함께 바뀌었습니다. var()는 지금의 변수 값을 꺼내기 때문입니다. 두 :root 규칙은 선택자가 같아 명시도가 같고, 조건이 맞으면 나중에 쓴 조건 블록 쪽이 이깁니다(브라우저 규칙 5). 조건이 맞지 않으면 블록이 닿지 않아 블록 밖 :root의 밝은 값이 남습니다.

light 값은 밝은 화면을 골랐거나 따로 고르지 않은 기기입니다. 밝은 값은 블록 밖 :root에 이미 있어 light 블록은 따로 쓰지 않습니다.

2color-scheme으로 브라우저가 그리는 칸도 맞춘다

변수는 우리가 var()로 칠한 곳만 바꿉니다. 입력 칸 · 체크박스 · 라디오 · 스크롤 막대는 브라우저가 기본 모양으로 그려, 어두운 페이지에서도 흰 채로 남습니다. color-scheme: light dark⁠는 이 페이지가 밝은 화면과 어두운 화면을 모두 지원한다고 브라우저에 알립니다 — 그러면 브라우저가 고른 화면에 맞는 칸 모양을 그립니다.

<label for="q">요리 찾기</label>
<input id="q" />
:root {
  color-scheme: light dark;
}
결과 화면
잰 값개발자 도구에서 본 값
  • #q의 color-schemelight dark
  • #q의 background-colorrgb(59, 59, 59)

어두운 화면에서 입력 칸이 어두운 회색으로 그려졌습니다(크롬 기준 — 칸의 기본 모양은 브라우저마다 다릅니다). 이 줄이 없으면 칸은 흰 바탕 그대로입니다. color-scheme은 물려받는 속성이라 :root에 한 번 두면 안의 모든 칸이 따릅니다(브라우저 규칙 5). 17단원 할 일 화면의 체크박스도 이 줄로 어두워집니다.

비슷해 보이는 셋은 하는 일이 다릅니다.

코드하는 일
@media (prefers-color-scheme: dark)어두운 화면을 골랐을 때 닿을 규칙을 고른다
color-scheme: light dark두 화면을 지원한다고 알려, 브라우저가 그리는 칸 모양을 맞춘다
var(--surface) 같은 꺼내기우리가 정한 변수 값으로 칠한다

3개발자 도구로 두 화면을 견준다

어두운 화면은 운영체제의 화면 모양 설정(밝게 · 어둡게)을 따릅니다. 페이지만 견줄 때는 개발자 도구의 Rendering에서 Emulate CSS media feature prefers-color-scheme 목록을 prefers-color-scheme: dark⁠로 골라 흉내 냅니다(크롬 기준 — 여는 길은 움직임 줄이기 3절). 보조 글자 · 입력 칸 · 포커스 윤곽선까지 두 화면에서 읽히는지 보고, 끝나면 No emulation으로 되돌립니다.

예제

레시피 노트 다크 모드 — 하위 목표 셋

다크 모드 틀의 하위 목표는 셋입니다 — 색을 변수로 모으기 → 어두운 값 → color-scheme. 하위 목표 1은 11단원에서 한 일입니다.

변수역할밝은 값어두운 값
--bg페이지 바탕#f5f5f7#121820
--surface바탕 위의 면(카드 · 칸)#ffffff#1d2633
--text글자#1d1d1f#f1f4f8
--muted보조 글자#6e6e73#a3adb8
--line선#d2d2d7#3a4552
--accent링크 · 강조 · 단추#005bb5#9ac7ff
<div class="card">
  <h2>김치볶음밥</h2>
</div>
<a href="kimchi.html">자세히</a>
<label for="q">요리 찾기</label>
<input id="q" />
/* 1. 색을 변수로 모으기 */
:root {
  --bg: #f5f5f7;
  --surface: #ffffff;
  --text: #1d1d1f;
  --muted: #6e6e73;
  --line: #d2d2d7;
  --accent: #005bb5;
}
결과 화면

색을 쓰는 곳은 모두 변수에서 꺼냅니다.

body {
  color: var(--text);
  background-color: var(--bg);
}

.card {
  background-color:
    var(--surface);
  border: 1px solid var(--line);
}

a {
  color: var(--accent);
}
결과 화면

어두운 화면 조건 블록에서 여섯의 값만 바꿉니다. 카드 · 링크 규칙은 블록 안에 옮겨 적지 않습니다.

/* 2. 어두운 값 */
@media (
  prefers-color-scheme: dark
) {
  :root {
    --bg: #121820;
    --surface: #1d2633;
    --text: #f1f4f8;
    --muted: #a3adb8;
    --line: #3a4552;
    --accent: #9ac7ff;
  }
}
결과 화면
잰 값개발자 도구에서 본 값
  • body의 background-colorrgb(18, 24, 32)
  • .card의 background-colorrgb(29, 38, 51)
  • a의 글자색rgb(154, 199, 255)
  • #q의 background-colorrgb(255, 255, 255)

바탕 · 카드 · 링크는 어두워졌는데 입력 칸만 흰 채입니다. 마지막 한 줄로 칸 모양을 맞춥니다. 첫 :root의 끝에 넣어도 같습니다.

/* 3. color-scheme */
:root {
  color-scheme: light dark;
}
결과 화면
잰 값개발자 도구에서 본 값
  • #q의 background-colorrgb(59, 59, 59)

17단원 할 일 화면은 여기에 --danger(밝은 #d70015 · 어두운 #ff6b6b)를 더해 변수 일곱의 값을 바꿉니다.

자주 틀리는 곳

틀린 생각: 어두운 화면은 규칙마다 어두운 색을 새로 적어 만든다.

<p>김치볶음밥</p>
<a href="kimchi.html">자세히</a>
a {
  color: #005bb5;
}

@media (
  prefers-color-scheme: dark
) {
  body {
    color: #f1f4f8;
    background-color: #121820;
  }
}
결과 화면
잰 값개발자 도구에서 본 값
  • p의 글자색rgb(241, 244, 248)
  • a의 글자색rgb(0, 91, 181)

body만 옮겨 적고 a를 놓쳤습니다. 어두운 바탕 위에 짙은 파랑 링크가 남아 읽기 어렵습니다.

맞는 규칙: 색을 쓰는 규칙은 그대로 두고 변수 값만 바꿉니다. 변수를 꺼내 쓰는 곳은 모두 함께 바뀝니다.

틀린 생각: 변수 이름은 밝은 화면의 색 이름으로 지어도 된다.

:root {
  --white: #ffffff;
}

@media (
  prefers-color-scheme: dark
) {
  :root {
    --white: #1d2633;
  }
}

어두운 화면에서 --white는 짙은 남색을 담습니다. var(--white)로 칠한 카드를 보며 흰 카드를 떠올리게 돼, 이름이 거짓말을 합니다.

맞는 규칙: 변수 이름은 역할로 짓습니다(--surface). 두 화면에서 값은 달라도 역할은 같습니다.

틀린 생각: color-scheme: light dark⁠만 적으면 페이지가 알아서 어두워진다.

<p>김치볶음밥</p>
:root {
  --bg: #f5f5f7;
  color-scheme: light dark;
}

body {
  background-color: var(--bg);
}
결과 화면
잰 값개발자 도구에서 본 값
  • body의 background-colorrgb(245, 245, 247)
  • p의 글자색rgb(255, 255, 255)

바탕은 우리가 정한 밝은 값 그대로인데, 글자는 브라우저가 어두운 화면에 맞춰 흰색으로 그렸습니다. 밝은 바탕에 흰 글자가 되어 읽히지 않습니다.

맞는 규칙: color-scheme은 브라우저가 그리는 것(처음 글자색 · 칸 모양)만 맞춥니다. 우리가 칠한 색은 어두운 값을 변수로 함께 줍니다 — 하위 목표 2와 3은 둘 다 필요합니다.

확인 문제

1. 예제에서 조건 블록 안 --accent의 값을 #ffd60a(노랑)로 바꿉니다. '자세히' 링크는 두 화면에서 무슨 색일까요?

  • ① 어두운 화면에서만 노랑, 밝은 화면은 그대로 파랑
  • ② 두 화면 모두 노랑
  • ③ 두 화면 모두 그대로
힌트

바꾼 값이 조건 블록 안에 있는지 밖에 있는지 보세요.

정답

①입니다.

<a href="list.html">자세히</a>
:root {
  --accent: #005bb5;
}

a {
  color: var(--accent);
}
결과 화면
잰 값개발자 도구에서 본 값
  • a의 글자색rgb(0, 91, 181)
@media (
  prefers-color-scheme: dark
) {
  :root {
    --accent: #ffd60a;
  }
}
결과 화면
잰 값개발자 도구에서 본 값
  • a의 글자색rgb(255, 214, 10)
  • 조건 블록은 어두운 화면에서만 닿아, 밝은 화면의 --accent는 블록 밖 값 그대로입니다.
  • ②는 조건 블록이 늘 닿는다고 본 것입니다.

2. 밝은 값만 있는 페이지에 다크 모드 하위 목표 2를 씁니다. 어두운 화면에서 --bg는 #121820, --text는 #f1f4f8이 되게 ____ 자리에 블록 하나를 쓰세요.

:root {
  --bg: #f5f5f7;
  --text: #1d1d1f;
}

/* 2. 어두운 값 */
____
힌트

1절 둘째 CSS의 꼴을 따릅니다 — 조건 블록 안에 같은 선택자를 둡니다.

정답
<p>잡채</p>
:root {
  --bg: #f5f5f7;
  --text: #1d1d1f;
}

body {
  color: var(--text);
  background-color:
    var(--bg);
}
결과 화면
/* 2. 어두운 값 */
@media (
  prefers-color-scheme: dark
) {
  :root {
    --bg: #121820;
    --text: #f1f4f8;
  }
}
결과 화면
잰 값개발자 도구에서 본 값
  • body의 background-colorrgb(18, 24, 32)
  • p의 글자색rgb(241, 244, 248)
  • 블록 안 선택자도 :root입니다. body 규칙을 블록 안에 옮겨 적을 필요가 없습니다.
  • 조건 블록은 밝은 값 :root 뒤에 둡니다. 위에 두면 나중에 쓴 밝은 값이 이깁니다.

3. 플로우 카페 페이지입니다. 주문 폼에만 color-scheme을 주었습니다. 어두운 화면에서 브라우저가 어두운 칸 모양으로 그리는 칸을 모두 고르세요.

<label>찾기 <input /></label>
<form class="order">
  <label>메뉴 <input /></label>
  <label>잔 수 <input /></label>
</form>
.order {
  color-scheme: light dark;
}
결과 화면
  • ㉠ 찾기 칸
  • ㉡ 메뉴 칸
  • ㉢ 잔 수 칸
힌트

color-scheme을 받은 요소와 그 안에 든 요소를 나무에서 찾아보세요.

힌트 둘

color-scheme이 물려받는 속성인지 2절에서 찾아보세요.

정답

㉡ · ㉢입니다.

잰 값개발자 도구에서 본 값
  • body > label input의 color-schemenormal
  • .order input의 color-schemelight dark
  • .order label:last-child input의 color-schemelight dark
  • color-scheme은 물려받는 속성이라 form 안의 두 칸이 그 값을 받습니다. 폼 밖 찾기 칸은 처음 값 normal이라 어두운 화면에서도 밝은 칸 모양입니다.
  • ㉡만 골랐다면 첫 자식만 물려받는다고, ㉠도 골랐다면 페이지 전체에 닿는다고 본 것입니다. 페이지 전체에 주려면 :root에 둡니다.

컴퓨터에서 해 보기(선택) — 점수에 들지 않습니다. 폰으로만 보고 있다면 건너뛰세요. 예제의 HTML과 네 CSS를 index.html과 style.css로 저장하고 link로 이어 엽니다(문서 뼈대의 body 안에 예제 HTML을 넣습니다). 3절대로 개발자 도구에서 어두운 화면을 흉내 내고, 마지막 :root 규칙의 color-scheme 줄을 지운 뒤 저장하고 새로고침합니다. 입력 칸의 모양을 줄을 지우기 전과 견줘 보고, 끝나면 흉내를 No emulation으로 되돌립니다.

끝까지 내려오면 저절로 표시됩니다. 이 기기에만 저장합니다.

HTML·CSS 목차로