잘 만든 테마 전환은 색을 곳곳에 박아 두 벌 만드는 게 아니라, 의미별 토큰 한 묶음만 갈아끼우면 화면 전체가 retone되도록 설계하는 것입니다. 여기에 두 테마 모두에서 대비를 지키는 접근성 검증과, OS 선호를 존중하는 prefers-color-scheme·light-dark()가 합쳐져야 비로소 완성됩니다.
요약
- 테마 전환 = 토큰(개발) + 대비 유지(컬러·접근성) + 사용자 선호(prefers-color-scheme)의 합작이다.
- 색을 의미(배경·표면·텍스트·강조)별 토큰으로 묶으면, 한 곳만 바꿔 전체가 retone된다.
- 다크는 색 반전이 아니다. 두 테마를 각각 WCAG 대비 기준으로 따로 검증해야 한다.
light-dark()·color-scheme으로 OS 선호를 자동으로 따르되, 사용자의 명시적 선택을 우선한다.
“다크 모드 추가해 주세요.” 짧은 요청이지만, 사이트가 색을 어떻게 다뤄왔느냐에 따라 한나절 일이 되기도, 일주일 일이 되기도 합니다. 색을 화면 곳곳에 직접 박아 두었다면 사실상 사이트를 한 번 더 칠해야 합니다. 반대로 처음부터 색을 토큰으로 묶어 두었다면, 토큰 묶음 하나를 바꾸는 것으로 끝납니다. 같은 “다크 모드”인데 비용이 열 배 차이가 나는 이유입니다.
테마 전환은 정확히 무엇이 바뀌는 건가요?
화면의 레이아웃·글자·간격은 그대로입니다. 바뀌는 건 오직 색입니다. 그래서 테마 전환을 잘하는 첫걸음은 “구조와 색을 분리”하는 것입니다. 버튼의 모양·크기·여백은 구조, 버튼의 배경색·글자색은 색. 색만 따로 떼어 변수로 모아두면, 그 변수 묶음을 통째로 바꿔 테마를 만듭니다.
그래서, 직접 눌러보세요
설명보다 빠릅니다. 아래 위젯에서 테마 버튼을 눌러보세요. 카드의 구조는 그대로인데, 색 토큰 묶음만 바뀌어 전체가 한 번에 retone되는 걸 볼 수 있습니다.
토큰만 갈아끼우는 다크/라이트
버튼으로 테마를 바꿔보세요. light-dark()로 더 간단해진 영역입니다.
검색과 AI에 찾아지게
같은 구조에 색 토큰만 바꿔 테마를 만듭니다.
왜 ‘토큰’이 합작의 중심인가요?
토큰은 색에 ‘이름’이 아니라 ‘역할’을 붙이는 일입니다. --bg(배경), --surface(카드·패널), --text(본문 글자), --accent(강조). 화면의 모든 요소가 이 역할 토큰을 참조하면, 테마를 바꾸는 건 토큰 값 한 묶음을 바꾸는 일이 됩니다. 색을 #1a1a1a처럼 곳곳에 직접 박았다면 다크 테마를 위해 그 모든 자리를 찾아 고쳐야 하지만, 토큰을 참조했다면 정의 한 곳만 바뀝니다. 이것이 “한 곳만 바꾸면 전체가 retone된다”의 실체입니다.
다크 모드는 색을 그냥 반전시키면 되지 않나요?
가장 흔한 오해입니다. 단순 반전은 대비를 깨고 그림자와 이미지를 어색하게 만듭니다. 다크 배경에는 순흑(#000) 대신 약간 들뜬 어두운 회색을 써 눈의 피로를 줄이고, 본문 글자도 순백 대신 살짝 낮춘 흰색을 씁니다. 강조색도 다크 배경에서는 채도를 조금 낮춰야 번쩍이지 않습니다. 결국 다크 테마는 ‘반전’이 아니라 역할 토큰을 각 환경에 맞게 다시 설계하는 일입니다 — 여기서 컬러 담당의 손이 들어갑니다.
어느 테마에서도 글자가 읽히게 하는 건 누구 몫인가요?
접근성입니다. 토큰을 바꾸면 ‘예뻐 보이는지’만 보고 끝내기 쉽지만, 진짜 검증은 두 테마 각각의 대비비입니다. 라이트에서 4.5:1을 넘겨도 다크에서 흐릿해지면 다크 사용자에게는 읽기 힘든 사이트입니다. 그래서 테마 토큰을 정할 때 라이트·다크 두 벌을 모두 대비 기준(본문 4.5:1, 큰 글자 3:1)에 통과시킵니다. 한 테마만 통과시키고 넘어가는 게 가장 흔한 사고입니다.
사용자가 좋아하는 화면은 어떻게 아나요?
물어볼 필요 없이, 브라우저가 이미 알고 있습니다. 사용자의 OS 설정이 prefers-color-scheme: dark로 전달되거든요. :root에 color-scheme: light dark를 선언하면 브라우저에 “두 스킴을 지원한다”고 알리고, 스크롤바·기본 폼 컨트롤 같은 브라우저 UI 명암까지 알아서 맞춰집니다. 그다음 색 토큰을 light-dark()로 적으면, 라이트면 첫 값·다크면 둘째 값이 자동으로 적용돼 미디어 쿼리를 따로 쓸 필요가 줄어듭니다.
그래도 사용자가 직접 바꾸고 싶다면요?
그 의사가 OS 선호보다 우선합니다. 기본값은 prefers-color-scheme로 시스템을 따르되, 사용자가 토글을 누르면 그 선택을 저장해 다음 방문에도 유지합니다. 순서가 핵심입니다 — 시스템을 존중하되, 사람의 명시적 선택이 이긴다. 위 위젯의 ‘액센트’처럼 라이트·다크 외의 추가 테마도 같은 토큰 구조라면 버튼만 늘리면 됩니다.
합작 분해 — 누가 무엇을 했나
저는 테마 전환을 만들 때 ‘색의 어떤 일을 누가 책임지는지’부터 칸으로 나눕니다.
| 담당 | 이 모듈에서 한 일 |
|---|---|
| 토큰(개발) | 색에 역할 이름(--bg·--surface·--text·--accent)을 붙여, 토큰 한 묶음만 바꾸면 전체가 retone되게 구조화 |
| 컬러 | 다크를 단순 반전이 아니라 재설계 — 순흑 대신 들뜬 어두운 회색, 순백 대신 낮춘 흰색, 강조색 채도 하향 |
| 접근성 | 라이트·다크 두 테마 각각 텍스트/배경 대비를 WCAG(본문 4.5:1, 큰 글자 3:1)로 따로 검증 |
| 사용자 선호 | color-scheme·prefers-color-scheme로 OS 설정을 기본값으로 따르되, 사용자의 명시적 토글 선택을 우선·기억 |
한 테마만 통과시키고 넘어가는 게 가장 흔한 사고라, 저는 이 칸을 둘 다 따로 채웁니다.
그리고 저는 이 테마 한 모듈에 들어간 기술을 칩으로 펼쳐 둡니다 — 미디어 쿼리를 줄여주는 최신 CSS 위주입니다.
토큰·컬러·접근성·사용자 선호가 같은 약속 위에서 움직이게 고른 도구들입니다.
이 작품에 들어간 기술
이 한 번의 테마 전환에는 세 직무의 기술이 겹쳐 있습니다. 각각 더 깊게 다룬 글로 이어집니다.
- 디자인 토큰 · 테마 구조(개발) — 색에 역할 이름을 붙여 한 곳에서 전체를 제어.
light-dark()·color-scheme으로 OS 선호 자동 대응. - 컬러 시스템(컬러) — 다크를 ‘반전’이 아니라 환경별 재설계로. 자세히는 컬러 담당자의 노트.
- 대비·접근성(접근성) — 두 테마 모두 WCAG 대비 기준 통과 검증. 자세히는 접근성 담당자의 노트.
| 항목 | 하드코딩 색 | 토큰 테마 |
|---|---|---|
| 다크 모드 추가 | 모든 색 자리를 찾아 수정 | 토큰 한 묶음만 교체 |
| 유지보수 | 색 변경 시 누락 발생 | 정의 한 곳만 바꿔 전체 retone |
| 접근성 | 테마별 대비 검증 어려움 | 두 테마 토큰을 따로 검증 가능 |
| 일관성 | 같은 ‘회색’이 자리마다 다름 | 역할별 토큰으로 항상 동일 |
| OS 선호 대응 | 수동 분기 | light-dark()·color-scheme 자동 |
그래서, 합작이 답입니다
테마 전환은 “예쁜 다크 모드 하나 추가”가 아니라, 토큰·컬러·접근성·사용자 선호가 같은 약속 위에서 움직일 때만 매끄럽습니다. Findable에서는 색을 처음부터 토큰으로 설계하고, 두 테마를 접근성 기준으로 함께 검증하고, OS 선호까지 존중하도록 만듭니다. 당신의 사이트, 어떤 테마를 입혀 드릴까요?
다크 모드를 꼭 만들어야 하나요?
light-dark()를 지금 써도 안전한가요?
다크 모드는 그냥 색을 반전시키면 되나요?
테마를 바꾸면 접근성 대비는 누가 보장하나요?
사용자가 고른 테마를 기억하게 할 수 있나요?
이런 디테일로 사이트를 만듭니다
색을 토큰으로 설계하고, 두 테마를 접근성 기준으로 함께 검증하는 팀이 당신의 홈페이지를 만들면 어떨까요. 무료 진단으로 시작하세요.
무료 진단 받기light-dark()로 만드는 테마
color-scheme과 light-dark() 라이브 데모.
색은 감이 아니라 시스템입니다
컬러 토큰과 대비를 다루는 법.
접근성 담당자의 노트
대비·포커스·키보드까지 라이브로.
위 테마 위젯은 이 페이지에서 실제로 동작하는 코드입니다(외부 라이브러리 0, 공유 위젯 스크립트로 구동). 대비비 기준은 WCAG 2.1 일반 권고(본문 4.5:1, 큰 글자 3:1)이며, 브라우저 지원 범위는 시점에 따라 달라질 수 있습니다. 날조된 사례·수치는 사용하지 않았습니다.