내과 병동 · 간호사
라이트 · 다크 테마와 표면 맥락
같은 시멘틱 토큰이 테마(라이트 · 다크)와 표면 맥락(기본 · 카드 · 브랜드 · 반전)에 따라 어떤 조합이 되는지, 그리고 어떤 토큰 · 클래스를 골라야 하는지 찾아보세요.
토큰 · Web / Mobile · 2026-10-13 신설 · 라이트는 전환 전후 픽셀 동일(Playwright 16장 0 차이)
규칙 (강제) · 코딩 에이전트용
- 팔레트(
--color-{hue}-{step})는 테마 불변이다. 다크는 시멘틱 토큰이 팔레트의 다른 단계를 가리키는 것이고, 어떤 테마·맥락 블록에서도 팔레트를 재정의하지 않는다(뉴트럴 반전 금지). 다크 전용 hex 도 없다. - 색은 시멘틱으로 고른다 — 원시 팔레트 유틸은 글자·면·경계에 쓰지 않는다. 글자
text-label-*· 면bg-bg-*/.ds-surface--*· 경계border-line-*· 상태.ds-notice/.ds-label(상태 쌍 토큰) · 버튼 역할 클래스.bg-white·text-neutral-900·bg-orange-500같은 원시 유틸은 테마를 모른다 — 새 코드 금지, 기존은 파일을 건드릴 때만 이관. - 면이 있는 토큰은 짝으로 쓴다.
action-*는-fg(+-border),status-*는-bg·-fg·-border·-icon. 잉크 하나(text-status-success)를 본문 글자로 쓰지 않는다(흰 배경 2.99). - 표면 맥락은 컨테이너에 명시한다. 카드
.ds-surface--card· 브랜드 띠.ds-surface--brand· 반전 띠.ds-surface--inverse. 클래스가 면을 칠하고 안쪽 토큰을 재정의하므로 안의 버튼·글자·경계에 색을 지정하지 않는다. 맥락은 중첩하지 않는다. 「카드 위라서 버튼 색을 바꾸는」 화면별 지정은 금지 — 맥락 클래스가 그 일을 한다. - 버튼은 역할 클래스.
ds-btn--primary·--primary-soft·--strong·--muted·--tertiary·--normal(line) ·--danger·--danger-soft. 외형 이름(--dark·--white-bk·--white-or·--lightor·--red·--lightred)은 같은 규칙을 공유하는 별칭이라 위반이 아니지만 새 코드엔 쓰지 않는다.--white-or대신.ds-surface--brand안의--primary. - 테마 전환 UI 는 hereby Works(직원 앱)에만 둔다(2026-10-19). 관리자 앱 등 다른 앱은 다크 스타일을 대응해도 사용자용 테마 선택을 만들지 않는다.
- 테마는 옵트인. 앱
main.ts가 첫 페인트 전에applyThemePreference(readStoredThemePreference())(@hereby/components)를 부를 때만<html data-theme="dark">가 달린다.prefers-color-scheme미디어쿼리를 CSS 에 쓰지 않는다.system은 JS 가 푼다. 차트처럼 CSS 변수를 못 읽는 그림은hereby:theme-change를 듣고 다시 그린다. - 반투명은 합성 결과로 판정한다. 유리·딤·hover tint 의 합격은 렌더된 최종 색으로 잰다(이 페이지의 수치가 그것). 계산값으로 대신하지 않고, 글자에 반투명 색을 새로 만들지 않는다(브랜드 면 위 보조 글자는 불투명
orange-100). - 자리별 전용 토큰을 쓴다(2026-10-19 다크 점검). 늘 어두운 면(LNB · 워크스페이스 전환 ·
*-dark배지) 위 흰 글자 · 선 · 틴트는--ds-ink-white-*(정적 흰색) —--ds-glass-*는 모바일 유리라 다크에서 어두워진다. 목록 묶음 사이 두꺼운 띠(border-t-8)는border-line-section—line-*는 얇은 선용이라 다크에서 밝은 띠가 된다. 이니셜 아바타 · 아이콘 썸네일 · 강조 칸의 연한 주황 면은bg-bg-brand-tint/-soft. - 간격은
gap으로 준다 —<p>· 입력칸 사이space-y/mt-*는 조용히 사라진다. 레이어 밖p { margin: 0 }과 DS 입력칸 기본 규칙(margin: 0)이 Tailwind 여백 유틸을 이긴다. 클래스로 여백을 주는 제목 · 설명은div, 입력칸이 끼는 묶음은flex flex-col gap-*. - 라이트를 바꾸지 않는다. 구조 전환(A)은 픽셀 동일이 전제였고 지금도 그렇다. 라이트 값을 바꾸는 항목(notice info 파랑 · 기존 대비 미달)은 별도 승인(설계 문서 §10).
- 정본:
packages/static/styles/design-system.css「3. @theme static — semantic color aliases」 · 「테마 — 다크」 · 「표면 맥락」 블록. JS:packages/components/src/theme.ts. 설계:docs/superpowers/specs/2026-10-12-color-theme-token-design.md.
Playground — 테마 × 표면 맥락
체크리스트 2/7 단계 · 첫 로그인 0%
체크리스트 2/7 단계 · 첫 로그인 0%
data-theme과 컨테이너의 ds-surface--* 클래스뿐입니다. 버튼은 --color-action-*, 글자는 --color-label-*, 경계는 --color-line-* 토큰을 읽고, 맥락 클래스가 그 토큰을 재정의해요. 브랜드 면은 양 테마에서 주황을 유지하고(브랜드 불변), 반전 면은 라이트에서 어둡고 다크에서 밝아요. <!-- 테마는 앱이 <html data-theme="dark"> 를 달 때만 바뀐다(옵트인). 맥락은 컨테이너의 클래스 하나. -->
<section class="ds-surface--card p-6 rounded-8">
<div class="text-b3 font-semibold text-label-normal">직원 앱 도입</div>
<p class="text-b5 text-label-assistive">체크리스트 2/7 단계 · 첫 로그인 0%</p>
<div class="ds-notice ds-notice--warning mt-3">
<span class="ds-ico ds-notice__icon" aria-hidden="true" x-html="$icon('ic-warning')"></span>
<span class="flex-1">아직 초대하지 않은 직원이 9명 있어요.</span>
</div>
<div class="mt-4 flex gap-2">
<button class="ds-btn ds-btn--solid ds-btn--tertiary ds-btn--md">나중에</button>
<button class="ds-btn ds-btn--line ds-btn--normal ds-btn--md">자세히</button>
<button class="ds-btn ds-btn--solid ds-btn--primary ds-btn--md">이어서 하기</button>
</div>
</section>무엇이 바뀌었나
| 전 | 후 |
|---|---|
@theme inline 이라 .text-label-normal{color:var(--color-neutral-900)} 로 박혀 시멘틱을 바꿔도 유틸리티에 안 닿음 | @theme static — 유틸리티가 var(--color-label-normal) 을 참조하고, [data-theme="dark"] 가 그 변수를 바꾼다 |
| 다크·테마 장치 없음 | [data-theme="dark"] 블록 하나(시멘틱 값만) + theme.ts 옵트인 |
버튼 색이 겉모양 이름(dark · white-bk)이고 팔레트 직접 참조 | 역할 토큰 --color-action-* + 역할 클래스, 외형 이름은 별칭 |
| 상태색이 잉크 하나 | 잉크 · 틴트 면 · 틴트 위 글자 · 경계 · 아이콘 쌍 |
「카드 위인지」를 화면이 알아서 white-or 등을 골라 씀 | 컨테이너의 .ds-surface--card/--brand/--inverse 가 안쪽 토큰을 재정의 |
tokens.css 가 팔레트·시멘틱 113개를 다시 선언(KEEP IN SYNC) | 삭제 — @theme static 이 :root 에 전부 내보냄 |
입력칸 #fff · 스위치 손잡이 #ffffff · 라벨 rgb(193 193 193/…) · 포커스 링 …, white) 등 정본 안 하드코딩 | 전부 토큰(값 동일) — 남은 literal 은 토큰 정의 안의 것뿐 |
계층과 명명
① 팔레트 --color-{hue}-{step} @theme static 테마·맥락 불변
② 파운데이션 (두지 않음 — 팔레트가 이미 역할 중립)
③ 시멘틱 --color-{group}-{role}[-{part}] @theme static 테마가 값을 바꾸고 맥락 클래스가 재정의
④ 컴포넌트 --ds-{component}-{part} :root ③을 참조만 한다| 그룹 | 뜻 | 기본값의 주인 | 짝 접미사 |
|---|---|---|---|
bg | 표면 | 면 | — (글자는 label) |
label | 글자·아이콘 잉크 | 잉크 | — |
line | 경계 | 잉크 | — |
action | 버튼·칩 채움 | 면 | -fg · -border · -soft · -soft-fg |
status | 상태 의미색 | 잉크 | -bg · -fg · -border · -icon |
primary | 브랜드 | 면 | -fg |
static | 테마 불변 흑백 | — | --color-static-white · -black |
요청서의 text / surface / border 는 각각 label / bg / line 이에요 — 이미 1,768곳이 쓰는 어휘라 개명하지 않았습니다.
테마 켜기
// apps/<app>/src/main.ts — 첫 페인트 전, Alpine.start() 전
import { applyThemePreference, readStoredThemePreference } from "@hereby/components";
applyThemePreference(readStoredThemePreference()); // 기본 "light" — 부르지 않는 앱은 영원히 라이트- 선택값:
"light" | "dark" | "system", localStoragehereby.theme(기기별). 설정 화면은persistThemePreference를 따로 부른다. 사용자에게 테마 전환 기능을 주는 앱은 hereby Works(직원 앱) 하나뿐이다(2026-10-19 결정) — 「더보기 → 화면 설정 → 화면 테마」. 관리자 앱은 다크 스타일을 대응해 두되 전환 기능을 주지 않는다: 부팅 적용은 사용자 키가 아니라 QA 전용 키를 읽어(readStoredThemePreference(ADMIN_THEME_QA_STORAGE_KEY)) 기본은 늘 라이트이고, 다크는 개발 · QA 때 localStoragehereby.theme.admin-qa=dark를 넣어 확인한다. 관리자 앱은hereby.theme를 읽지 않는다 — 운영에서 관리자 앱(/)과 직원 앱(/employee-app/)은 같은 출처라 localStorage 를 함께 써서, 읽으면 Works 에서 고른 다크가 전환 기능이 없는 관리자 앱으로 샌다(2026-10-20). 다른 앱(hr-system · team-manager · 총무 등)도 전환 기능을 만들지 않는다. - 다크 값 요약 — 위계는 「높을수록 밝게」: 캔버스 900 < 기본·카드 800 < 모달 700. 잉크 400 · 틴트는 색을 표면(neutral-800)에 섞은 색 — 브랜드 선택 면 14% · 연한 주요 · 썸네일 22%, 상태 틴트 16% · 상태 경계 40%(2026-10-19~20, 전엔 900 · 700) · 틴트 위 글자 200 · 경계는 새 단계 neutral-450(3.10). orange 주요 버튼과 짙은 색 버튼(strong · danger · 의미색 솔리드)은 다크에서도 채움 그대로 + 흰 글자·아이콘(2026-10-13 결정). strong 은 짙은 면 위라 가는 경계(neutral-450)를 켠다.
- 어떤 요소에도
data-theme="dark"를 달 수 있어요(이 페이지의 나란히 비교가 그 방식). 앱에서는<html>에만. - 모든 컴포넌트 페이지에서 다크로 확인 — Playground 툴바의 「미리보기 테마」로 그 페이지의 모든 Playground(미리보기 · 모아보기)를 라이트 · 다크로 바꿔요. 미리보기 영역에
data-theme을 다는 방식이라 앱의 다크와 같은 값입니다. 컴포넌트를 고치면 그 페이지에서 라이트 · 다크를 번갈아 눌러 확인합니다. - 문서 사이트 테마와 미리보기 테마 (2026-10-20) — 오른쪽 위 사이트 토글(VitePress)을 다크로 바꾸면
<html data-theme="dark">가 붙어 본문 · 표 · 데모까지 DS 다크 값으로 그려져요. 미리보기 테마는 사이트에 상대적이라(「같게」 · 「반대로」를 기억), 사이트를 바꾸면 라이트로 보던 미리보기는 다크로, 다크로 보던 것은 라이트로 뒤집혀요. 다크 페이지 안의 라이트 프레임은 docs 브리지의[data-theme="light"]블록(문서 전용, 생성물)이 토큰을 되돌려 그려요. 그래서 문서 데모 CSS 는--vp-c-*(사이트 색)나 팔레트 면을 쓰지 않고 시멘틱 토큰을 씁니다 — 사이트 색은 미리보기 테마를 따르지 않아 반대 테마 미리보기에서 어긋나요. - 늘 어두운 면 위 흰 잉크는
--ds-ink-white-*— LNB 사이드바 · 워크스페이스 전환 ·*-dark배지처럼 테마와 무관하게 어두운 면에 올라가는 흰 글자 · 선 · 틴트는--ds-glass-*(모바일 유리, 다크에서 어두워짐)가 아니라--ds-ink-white-04 … -70(정적 흰색 기반)을 써요.
표면 맥락
<section class="ds-surface--card rounded-8 p-6"> <!-- 카드: tertiary 버튼이 한 단계 내려가고 경계가 켜짐 -->
<div class="ds-surface--brand p-6"> <!-- 브랜드 띠: primary 가 흰 채움 + orange-600 글자 -->
<nav class="ds-surface--inverse"> <!-- 반전 띠: strong 이 면의 반대색 -->맥락 클래스는 시멘틱 토큰만 재정의하고 컴포넌트 변수(--ds-btn-*)는 건드리지 않아요. 그래서 버튼뿐 아니라 링크 · 칩 · 아이콘 · 경계가 함께 맞습니다.
Playground — 사례 6
<!-- 전 -->
<div class="bg-white p-6 rounded-8">
<button class="ds-btn ds-btn--solid ds-btn--white-bk ds-btn--md">취소</button>
<button class="ds-btn ds-btn--solid ds-btn--dark ds-btn--md">확인</button>
</div>
<!-- 후 -->
<div class="ds-surface--card p-6 rounded-8">
<button class="ds-btn ds-btn--solid ds-btn--tertiary ds-btn--md">취소</button>
<button class="ds-btn ds-btn--solid ds-btn--strong ds-btn--md">확인</button>
</div>사례별 판단
| 사례 | 라이트 | 다크 | 맞는 수단 | 아닌 수단 |
|---|---|---|---|---|
| ① 카드 위 묻히는 버튼 | 흰 버튼 / 흰 카드 1.00 | 같은 구조 | 표면 맥락 분리 (.ds-surface--card) | 테마 값 조정 · 새 variant |
| ② 브랜드 배경 위 동일 계열 | 주황 위 주황 | 동일 | 표면 맥락 분리 (.ds-surface--brand) | 화면별 white-or |
| ③ 다크에서 과한 밝기·채도 | — | 50 틴트·600 잉크가 뜸 | 테마별 값 조정(시멘틱이 400 · 900 단계 참조) | 팔레트 반전 |
| ④ 경계가 사라지는 입력·보조 버튼 | 테두리 1.26 | 1.73 | 조합 변경(면·테두리·라인 버튼이 같은 토큰) + 필요 시 팔레트 단계 추가 (D7) | 입력칸 전용 색 |
| ⑤ 상태 표시 | 잉크를 글자로 2.99 | 600 잉크 미달 | variant 추가(잉크/bg/fg/border/icon 쌍) + 아이콘 | 잉크 하나 겸용 |
| ⑥ 반투명 | 유리 위 글자 가변 | 흰 유리가 밝은 막 | 측정 후 베이스 조정(--color-bg-glass) | 계산값으로 합격 |
Playground — 버튼 × 표면 맥락 × 상태 × 테마
<div class="ds-surface--card p-6 rounded-8">
<button class="ds-btn ds-btn--solid ds-btn--primary ds-btn--md">저장</button>
</div>판정 대상 0칸 중 0칸 통과(글자 4.5 이상 그리고 경계 3 이상). 비활성 칸은 제외.
읽는 법
- 수치는 렌더된 색으로 계산해요. 글자 = computed
color, 면 =background-color에 hover/pressed 의 inset 오버레이를 합성한 최종 색, 경계 = 면 vs 뒤 면과 테두리 vs 뒤 면 중 큰 값. - 기준: 글자 4.5 · 경계 3. 비활성은 WCAG 1.4.3 예외라 제외. hover · pressed · focus 는 DS 규칙과 같은 선언의 강제 클래스로 고정했어요(마우스 없이 측정).
- 미달로 남는 것(결정으로 수용):
primary흰 글자 / orange-500 3.18(양 테마 — 브랜드 색 유지) ·danger흰 글자 / red-600 4.33 ·tertiary의 경계(기본 면에서 1.00~1.26 — 카드 맥락에서는 경계가 켜짐). 라인 버튼 경계는 neutral-450 으로 4.99(라이트) · 3.10(다크). 수치를 숨기지 않고 그대로 둡니다.
Playground — 대표 화면 라이트 · 다크
외래 · 간호조무사
원무과 · 행정
내과 병동 · 간호사
외래 · 간호조무사
원무과 · 행정
ds-surface--card, 버튼은 역할 클래스, 상태는 notice·라벨 쌍 토큰, 모달 면은 --color-bg-elevated. 다크에서 모달 면(700)이 카드(800)보다 밝고, 페이지 캔버스(900)가 가장 어두운 「높을수록 밝게」 위계를 확인하세요. <div class="bg-bg-alternative p-4">
<div class="ds-filter-bar">…</div>
<article class="ds-surface--card rounded-8 p-4">
<div class="flex items-center gap-2">
<span class="text-b4 font-semibold text-label-normal">김민지</span>
<span class="ds-label ds-label--sm ds-label--green">재직</span>
</div>
<p class="text-b6 text-label-assistive">내과 병동 · 간호사</p>
<div class="flex justify-end gap-2 mt-3">
<button class="ds-btn ds-btn--solid ds-btn--tertiary ds-btn--sm">상세</button>
<button class="ds-btn ds-btn--solid ds-btn--strong ds-btn--sm">승인</button>
</div>
</article>
</div>확인할 것
- 카드 목록: 캔버스(
bg-alternative) 위 카드(.ds-surface--card)가 다크에서 한 단계 밝고,tertiary버튼에 경계가 생기며, 라벨 틴트가 900 단계로 가라앉아요. - 폼: 입력칸 면 = 카드 면, 테두리 = 라이트
line-normal(#e5e5e5) · 다크line-field(neutral-500 #5e5e5e — 2026-10-20 450 에서 한 단계 내림), 포커스 =line-dark, 오류 =status-error. 체크·라디오·스위치의 켜진 채움은label-normal(다크에서 밝은 회색)이라 글리프는 어두운 체크로 바뀝니다. - 상태 표시: info 는 파랑 쌍(blue-50/200/700 · 다크 900/700/200), warning 은 노랑 — 2026-10-13 부터 색으로 구분돼요. 바 notice · notice · 라벨 전부 같은 상태 쌍을 읽어요.
- 모달: 모달 면(700) > 카드(800) > 캔버스(900). 푸터는
tertiary+strong, 크리티컬 컨펌은primary-soft. - 모바일 (hereby Works): 앱 헤더 면 =
--ds-app-header-surface(앱이bg-alternative/bg-container로 정함) · 유리 캡슐 =--color-bg-glass· 하단 내비 면 =--ds-bottom-nav-surface· 선택 바탕 =bg-sunken(다크bg-hover) · 홈 카드 =.ds-surface--card· 할 일 칩 =primary-soft· 강조 숫자 =text-label-brand. 직원 앱은 셸(main.css)과 모든 화면(근무 · 메신저 · 결재 · 더보기 · 2depth · 신청서 · 로그인)의 면 · 글자 · 선 · 썸네일을 토큰으로 이관해 다크가 실제로 적용돼요(2026-10-19). 사람 이니셜 · 아이콘 썸네일 · 강조 칸의 연한 주황 면은bg-brand-tint/bg-brand-tint-soft를 써요. 보조 글자는label-muted(다크 400) · 비활성 글자는label-disable(다크 450) 이에요.
Playground — 토큰 표
| 토큰 | 역할 | 사용 맥락 | 라이트 | 다크 | 다크 매핑 | 짝 |
|---|---|---|---|---|---|---|
--color-action-primary | 주요 채움 | ds-btn--primary | | | → | action-primary-fg |
--color-action-primary-fg | 주요 글자 | ds-btn--primary | | | → | action-primary |
--color-action-primary-soft | 연한 주요 채움 | ds-btn--primary-soft (= lightor) | | | → | action-primary-soft-fg |
--color-action-strong | 중립 강조 채움 | ds-btn--strong (= dark) · 서브 탭 선택 | | | → | action-strong-fg |
--color-action-strong-fg | 중립 강조 글자 | ds-btn--strong — 다크에서도 흰색 | | | → | action-strong |
--color-action-strong-border | 중립 강조 경계 | 다크에서 짙은 면 위 검정 버튼을 가르는 선(라이트 transparent) | | | → | action-strong |
--color-action-muted | 회색 채움 | ds-btn--muted | | | → | action-muted-fg |
--color-action-tertiary | 물러남 채움 | ds-btn--tertiary (= white-bk) · 취소·닫기 | | | → | action-tertiary-fg · -border(맥락이 켬) |
--color-action-outline-border | 라인 기본 경계 | ds-btn--line ds-btn--normal | | | → | action-outline-fg |
--color-action-danger | 위험 채움 | ds-btn--danger (= red) — 다크에서도 red-600 + 흰 글자 | | | → | action-danger-fg |
--color-action-danger-soft | 연한 위험 면(= lightred) | ds-btn--danger-soft — 다크 red-900 | | | → | action-danger-soft-fg |
--color-action-danger-soft-fg | 연한 위험 글자 | ds-btn--danger-soft 글자 — 다크 red-200 | | | → | action-danger-soft |
--color-action-assistive | 옅은 회색 채움 | ds-btn--assistive — 다크 500 | | | → | action-assistive-fg |
--color-action-strong-hover | 중립 강조 hover | ds-btn--strong hover · 체크박스/라디오 체크 hover — 다크 200 | | | → | action-strong-fg |
--color-action-primary-soft-fg | 연한 주요 글자 | ds-btn--primary-soft 글자 — 다크 orange-300 | | | → | action-primary-soft |
--color-action-muted-fg | 회색 채움 글자 | ds-btn--muted 글자 | | | → | action-muted |
--color-action-assistive-fg | 옅은 회색 글자 | ds-btn--assistive 글자 | | | → | action-assistive |
--color-action-tertiary-fg | 물러남 글자 | ds-btn--tertiary 글자(= label-normal) | | | → | action-tertiary |
--color-action-tertiary-border | 물러남 경계 | 카드 맥락(.ds-surface--card)에서만 보임 — 기본 transparent | | | → | action-tertiary |
--color-action-outline-fg | 라인 기본 글자 | ds-btn--line.ds-btn--normal 글자 | | | → | bg-container |
--color-action-danger-fg | 위험 글자 | ds-btn--danger 글자 — 흰색 불변 | | | → | action-danger |
--color-action-overlay-strong | pressed 오버레이 잉크 | 버튼 :active 오버레이 | | | → | — |
--color-action-disabled-fg | 비활성 글자 | disabled 버튼 글자 — 다크 450(10/19) | | | → | action-disabled |
--color-action-green | 의미색 솔리드 채움(green · blue · purple · aquablue · yellow) | ds-btn--solid ds-btn--green 등 — 테마 불변, 흰 글자(yellow 는 어두운 글자) | | | → | action-{hue}-fg |
--color-action-blue | 의미색 솔리드 파랑 | ds-btn--blue — 테마 불변 + 흰 글자 | | | → | static-white |
--color-action-purple | 의미색 솔리드 보라 | ds-btn--purple — 테마 불변 | | | → | static-white |
--color-secondary-purple-bg | 보라 보조 면 | 필수(부서) 대화방 썸네일 — 글자는 secondary-purple | | | → | secondary-purple |
--color-action-aquablue | 의미색 솔리드 아쿠아 | ds-btn--aquablue — 테마 불변 | | | → | static-white |
--color-action-yellow | 의미색 솔리드 노랑 | ds-btn--yellow — 테마 불변, 글자 neutral-900 | | | → | label-normal |
--color-action-green-fg | 의미색 솔리드 글자 | green · blue · purple · aquablue 는 흰색, yellow 는 neutral-900 — 테마 불변 | | | → | action-green |
--color-action-blue-fg | 의미색 솔리드 파랑 글자 | 흰색 불변 | | | → | action-blue |
--color-action-purple-fg | 의미색 솔리드 보라 글자 | 흰색 불변 | | | → | action-purple |
--color-action-aquablue-fg | 의미색 솔리드 아쿠아 글자 | 흰색 불변 | | | → | action-aquablue |
--color-action-yellow-fg | 의미색 솔리드 노랑 글자 | neutral-900 불변 | | | → | action-yellow |
--color-action-icon-muted | 아이콘 버튼 muted 잉크 | ds-btn--icon ds-btn--muted(알림벨 · 새로고침) — label-muted 보다 한 단계 진함 | | | → | bg-normal |
--color-action-disabled | 비활성 채움 | 모든 :disabled 버튼 | | | → | action-disabled-fg |
--color-action-overlay | hover/focus 오버레이 잉크 | 모든 버튼(20% · 30%) | | | → | action-overlay-strong(pressed 10%) |
팔레트 단계별 다크 매핑 (전체 토큰)
라이트에서 쓰는 팔레트 단계마다 다크에서 어느 단계로 가는지 모은 표예요. 1:1 표시는 그 단계가 다크에서 한 단계로만 가고 그 다크 단계가 다른 라이트 단계에서 오지 않는 것. 같은 라이트 단계가 두 줄 이상으로 갈라지면 역할에 따라 다르게 매핑된 것이고(흰색은 면·글자·반전에서 모두 다르다), 1:1 이 아니어도 잘못이 아니에요 — 뒤집기 금지 원칙의 결과입니다.
| 라이트 단계 | 다크 단계 | 쓰는 토큰 | 1:1 |
|---|
이 그룹: 1:1 0 · 합침 0 · 역할별 0 · 불변 0. 「1:1」은 그 라이트 값이 늘 같은 다크 값으로 가고 그 다크 값은 거기서만 오는 토큰, 「합침」은 서로 다른 라이트 값이 한 다크 값으로 모이는 토큰(예: bg-normal 과 bg-container 가 다크에서 둘 다 neutral-800), 「역할별」은 같은 라이트 값이 역할에 따라 다른 다크 값으로 가는 토큰, 「불변」은 양 테마가 같은 토큰이에요.
--color-action-primary가 흰색이 되는 것을 볼 수 있어요. 팔레트 토큰(--color-orange-500 등)은 테마가 바뀌어도 그대로라 표에 없습니다. --color-action-primary: …; /* 다크 … */
--color-action-primary-fg: …; /* 다크 … */
--color-action-primary-soft: …; /* 다크 … */
--color-action-strong: …; /* 다크 … */
--color-action-strong-fg: …; /* 다크 … */
--color-action-strong-border: …; /* 다크 … */
--color-action-muted: …; /* 다크 … */
--color-action-tertiary: …; /* 다크 … */
--color-action-outline-border: …; /* 다크 … */
--color-action-danger: …; /* 다크 … */
--color-action-danger-soft: …; /* 다크 … */
--color-action-danger-soft-fg: …; /* 다크 … */
--color-action-assistive: …; /* 다크 … */
--color-action-strong-hover: …; /* 다크 … */
--color-action-primary-soft-fg: …; /* 다크 … */
--color-action-muted-fg: …; /* 다크 … */
--color-action-assistive-fg: …; /* 다크 … */
--color-action-tertiary-fg: …; /* 다크 … */
--color-action-tertiary-border: …; /* 다크 … */
--color-action-outline-fg: …; /* 다크 … */
--color-action-danger-fg: …; /* 다크 … */
--color-action-overlay-strong: …; /* 다크 … */
--color-action-disabled-fg: …; /* 다크 … */
--color-action-green: …; /* 다크 … */
--color-action-blue: …; /* 다크 … */
--color-action-purple: …; /* 다크 … */
--color-secondary-purple-bg: …; /* 다크 … */
--color-action-aquablue: …; /* 다크 … */
--color-action-yellow: …; /* 다크 … */
--color-action-green-fg: …; /* 다크 … */
--color-action-blue-fg: …; /* 다크 … */
--color-action-purple-fg: …; /* 다크 … */
--color-action-aquablue-fg: …; /* 다크 … */
--color-action-yellow-fg: …; /* 다크 … */
--color-action-icon-muted: …; /* 다크 … */
--color-action-disabled: …; /* 다크 … */
--color-action-overlay: …; /* 다크 … */라이트 값은 바뀌지 않았다
구조 전환(A 단계)은 값 참조만 바꿨으므로 라이트가 바뀌지 않아요 — 정본 CSS 를 바꾸기 전과 후를 Playwright 로 16장(관리자 5 · 직원 앱 3 · docs 8, 로그인 상태 포함) 찍어 픽셀 비교해 0 차이였습니다(Playwright 기본 임계: 픽셀당 YIQ 0.2 — 참조만 바꾼 A 단계는 값이 같아 임계와 무관하게 동일). 그 뒤 사용자 결정으로 라이트가 바뀐 것은 info notice 파랑 · 입력칸/셀렉트/라인 버튼 테두리(neutral-450) · muted 아이콘 버튼(500) · 직원 앱 hr-gray → neutral 이관(미세) 네 가지이고, 각각 diff 이미지로 그 영역만 바뀐 것을 확인한 뒤 기준선을 다시 떴습니다. --color-bg-elevated 의 라이트 값(#fdfdfd → #ffffff)은 쓰는 곳이 없어 화면 변화가 없습니다(결정 D4).
다크 값 — 점검 반영(2026-10-19)
직원 앱 41화면 · 관리자 앱 20화면 · 이 문서의 Playground 136개를 다크로 찍어 렌더된 색으로 대비를 계산하고 고친 값이에요. 라이트는 하나도 바뀌지 않았어요(관리자 앱 20화면 픽셀 비교). 위 토큰 표는 정본 CSS 를 직접 읽으므로 같은 값이 보여요. 결정 기록은 설계 문서 §13.
| 토큰 | 라이트 | 다크 | 쓰는 곳 · 이유 |
|---|---|---|---|
--color-label-muted | neutral-400 | neutral-400 (전 500) | 보조 문구 · 날짜 · 사번 · 도움말 · 서브 탭 숫자. 4.74 |
--color-label-disable | neutral-300 | neutral-450 (전 600) | 비활성 글자 · 구분점 · 메신저 시간. 3.10, 보조보다 옅음 |
--color-action-disabled-fg | label-disable | neutral-450 (전 500) | 비활성 버튼 글자. 바탕 700 위 2.8 |
--color-primary-bg | orange-50 | bg-brand-tint-soft (전 orange-900) | 선택 칸 · 선택 옵션 면 — 선택은 테두리 · 체크가 알림 |
--color-action-primary-soft | orange-50 | bg-brand-tint (전 orange-900) | 연한 주요 버튼(보기 · 작성 · 단체 채팅 · AI 추천) |
--color-bg-brand-tint | orange-100 | 주황 500 22% + neutral-800 | 이니셜 아바타 · 아이콘 썸네일 · 앱 헤더 아바타 |
--color-bg-brand-tint-soft | orange-50 | 주황 500 14% + neutral-800 | 강조 칸 · 옅은 주황 타일 |
--color-line-section | neutral-100 | neutral-900 | 목록 묶음 사이 두꺼운 띠 — 카드보다 가라앉음 |
--ds-ink-white-04 … -70 | 흰색 n% | 흰색 n%(불변) | LNB · 워크스페이스 전환 · *-dark 배지 위 흰 잉크 |
--color-status-{success,warning,error,info}-bg · --ds-secondary-{blue,purple}-bg | 50 단계 | 상태색 500 16% + neutral-800 (전 900) | 안내 박스 · 바 notice · 상태 배지 · 출근 완료 안내(2026-10-20) |
--color-status-*-border | 200 단계 | 상태색 500 40% + neutral-800 (전 700) | 안내 박스 · 라인 배지 테두리 |
컴포넌트별 다크 값
정본 CSS 에서 다크 동작이 바뀐 컴포넌트예요. 각 컴포넌트 페이지의 Playground 툴바 「미리보기 테마 → 다크」로 바로 볼 수 있어요.
| 컴포넌트 | 무엇 | 라이트 | 다크 |
|---|---|---|---|
| 앱 헤더 | 뒤로(<) 글자 | label-normal(#1b1b1b) | label-normal(밝은 글자) — 전엔 hr-gray-800 고정이라 묻힘 |
| 〃 | 아바타 원 · 이니셜 | bg-brand-tint · label-brand | 주황 22% 틴트 · orange-300 |
| 탭 바 | 비활성 탭 글자 --ds-tab-inactive-fg | neutral-300 | label-disable(450) |
| 배지 | ds-label--gray 글자 --ds-label-fg-assistive | label-assistive | label-alternative |
| 〃 | ds-label--gray-dark · --light-gray-dark | 흰 잉크(--ds-ink-white-*) | 같음(불변) |
| 버튼 | 라인 위험(--line.--danger) 글자 | secondary-red(red-600) | red-400 |
| 〃 | 비활성 글자 | label-disable | neutral-450 |
| 〃 | 연한 주요(--primary-soft / --lightor) 면 | orange-50 | bg-brand-tint |
| 폼 컨트롤 | 입력칸 hover 테두리 --ds-textfield-border-hover | line-strong(#c1c1c1) | line-alternative(400) |
| 필터 바 | 제목 .ds-filter-bar__title | label-alternative | neutral-300 |
| 〃 | 스크롤 화살표 · 검색 토글 면 | bg-container | neutral-800 |
| LNB 사이드바 | 메뉴 · 하위 메뉴 · 화살표 · 토글 글자/선 | 흰 잉크 70 · 60 · 55 · 18 | 같음(불변) — 전엔 다크에서 어두워져 사라짐 |
| 〃 | 접힘 플라이아웃 면 | bg-elevated | neutral-700 |
| 워크스페이스 전환 | 트리거 글자 · 테두리 · 틴트 | 흰 잉크 | 같음(불변) · 드롭다운 면 bg-elevated |
| 페이지 헤더 | 헤더 · 팀 선택 · 뒤로 · 새로고침 면 | bg-container | neutral-800 |
| 서브 탭 | 여러 개 선택(isActive) | — | — (모양은 단일 선택과 같음) |
목록 카드 .ds-list-card · 탭 바 화살표 · 소속 칩 | 면 | bg-container | neutral-800 |
| 스켈레톤 | 흰 면 · 회색 면(--on-sunken) · 떠 있는 면(--on-elevated) 위 자리표시 | --color-skeleton · -sunken · -elevated (neutral-75 · 100 · 75) | 600 40% + 800(≈#343434) · neutral-700 · 600 50% + 700(≈#3b3b3b) — 면보다 밝게, 차이 약 15 (10-21) |
| notice · 바 notice | 틴트 면 · 테두리 · 글자 · 아이콘 | status-*-bg/-border/-fg/-icon (50 · 200 · 700 · 500) | 상태색 500 16% · 40% + neutral-800 · 200 · 400 (10-20) |
| 라벨 · 배지 | 검정 라벨 칩 면 · 글자 · 흰 라벨 칩 면 | --ds-label-black-bg · -fg (label-normal · neutral-25) · --ds-label-white-bg(neutral-25) | action-strong(neutral-900) · 흰색 + 가는 경계 · bg-elevated(neutral-700) |
| 메신저 공용 · 직원 앱 상태 면 | 연결 상태 띠 · 경고/완료 원 아이콘 · 기념일 타일 · 부서 방 썸네일 | 상태색 쌍 status-*-bg / -fg / -icon · secondary-purple-bg | 상태색 500 16% 틴트 · 보라 16% |
| 하단 내비 | 면 · 윗선 · 분리형 캡슐 · 선택 유리 · 캡슐 테두리 · 그라데이션 끝 | bg-container · line-normal · --ds-bottom-nav-capsule-bg(bg-glass 66%) · --ds-bottom-nav-glow-bg(bg-glass 78%) · glass-55 · --ds-bottom-nav-surface(앱이 줌) | neutral-800 · 450 · neutral-900 92%(화면보다 어둡게, 10-20) · neutral-700 92% · 흰 잉크 10% · 앱 값 — 고정 흰색을 넘기면 흰 띠(10-20 직원 앱 메신저) |
| 대화 UI | 패널 · 입력줄 · 받은 말풍선 · 오류 말풍선 | bg-elevated · bg-container · bg-sunken · status-error-bg | neutral-700 · 800 · 900 · red 16% |
| AI 어시스턴트 | 이전 대화 목록 · 서랍 · 항목 hover | bg-alternative · bg-elevated · bg-hover | neutral-900 · 700 · 700 |
| 검색 입력 · 소속 칩 | 칩 면 · 글자 · × · 행 hover | bg-container · label-body · label-disable · label-normal 4% | neutral-800 · 100 · 450 · 4%(밝은 틴트) |
| 페이지네이션 | 버튼 글자 · hover · 현재 | label-alternative · bg-hover · bg-selected | 300 · 700 · 600 — ⚠ 앱별 복사본에 있음 |
| 모달 | 카드 면(.ds-modal · .ds-mini-modal) | bg-elevated #fff | neutral-700 — 마크업 bg-white 는 다크에서 흰 면으로 남으므로 지운다 |
| 툴팁 | 도움말 배지 테두리 · 글자 · hover 면 | line-strong · label-muted · bg-inverse | 300 · 400 · neutral-50(반전) |
| 데이터 테이블 | 목록 카드 · 비활성 카드 · 한 줄 셀 hover | bg-container · bg-alternative · bg-alternative | 800 · 900 · 900 |
| 모바일 대화 | 검색 줄 이동 버튼 · 대화방 바탕 | bg-container | neutral-800 |
| 로그인 · 가입 · 비밀번호 폼 · 게이트 화면 | 바깥 상자 · 아이콘 원 · 글자 | bg-container / bg-alternative · status-warning-bg · label-* | neutral-800 / 900 · yellow 16% · 밝은 글자 — 저장된 테마를 따름(Works) |
| 메신저 공용 | 루트 면 · 탭 줄 · 날짜 구분선 · 아바타 | bg-container · line-alternative · bg-brand-tint | neutral-800 · 400 · 주황 22% |
| 직원 추가 · 수정 패널 · 권한 모달 | 카테고리 내비 · 섹션 카드 · 모달 면 | bg-container / bg-alternative · bg-elevated | neutral-800 / 900 · 700 (관리자 앱 — QA 전용) |
| 내 정보 폼 | 일부 이관(25곳 보류 — 테마 옵트인 앱이 안 씀) | — | — |
| 근무표(Table 2) | 셀 면 · 주말 열 틴트 (admin main.css — 유일한 복사본) | bg-container · bg-brand-tint-soft 50% | neutral-800 · 주황 14% 50% |
접근성 판정 — 계산값과 측정값
| 구분 | 무엇 | 어디서 |
|---|---|---|
| 계산값 | 불투명 쌍의 WCAG 비(팔레트 단계 고르기용) | 설계 문서 §6 · §9 |
| 측정값 | 이 페이지가 렌더한 색으로 계산 — 반투명·오버레이·유리 포함 | 「사례 6」 · 「버튼 매트릭스」 |
| 기준 | 글자 4.5 · 큰 글자 3 · 경계/아이콘 3 · 비활성 제외 | — |
Playground — 바꿀 것 찾기
bg-white (페이지·카드)ds-surface--card (카드) · bg-bg-normal (페이지 면)bg-white 는 var(--color-white) 라 테마를 모른다(708곳). 카드는 맥락 클래스가 면과 안쪽 토큰을 함께 정한다.
bg-white · text-neutral-900
ds-surface--card · text-label-normal
bg-white · text-neutral-900
ds-surface--card · text-label-normal
/* 전 */ bg-white (페이지·카드)
/* 후 */ ds-surface--card (카드) · bg-bg-normal (페이지 면)계획
이관은 파일을 건드릴 때 그 파일 안에서만 한다(일괄 스윕 금지 — 버튼·모달 규칙과 같은 원칙). 켜지 않은 앱은 바뀌지 않으므로 순서는 자유다.
| 단계 | 대상 | 규모 | 메모 |
|---|---|---|---|
| 1 | docs 매트릭스 · admin 게이트 화면 · 드라이브(시멘틱 75%) | 파일럿 | D8 |
| 2 | bg-white → ds-surface--card / bg-bg-normal | 708곳(모달 177 은 CSS 가 면을 칠하므로 지우기만) | 카드면 맥락 클래스 |
| 3 | text-white → 맥락 안 text-label-normal / 역할 -fg | 99곳 | 테마 불변이어야 하면 text-label-white |
| 4 | 원시 뉴트럴 유틸 → text-label-* · border-line-* · bg-bg-* | 약 6,975곳 중 뉴트럴 계열 | 같은 값이라 라이트 무변화 |
| 5 | hr-* 팔레트 동결 · 직원 앱 이관 — 셸 CSS · 근무(홈) · 더보기 · 화면 설정(10-13, 216곳) + 결재 · 모달류 · 메신저 · 로그인 · 공용 파셜(10-19) 완료. 남은 것: 상태 배지 색(formatters.ts) | 직원 앱 | 매핑: text-hr-gray-900/800→label-normal · 700→label-body · 600→label-alternative · 500→label-assistive · 400→label-muted · 300→label-disable · bg-hr-gray-50→bg-alternative · 100→bg-hover · 200→bg-selected · bg-neutral-75→bg-sunken · border-hr-gray-100→line-normal · 200→line-alternative · bg-white→bg-container · text-orange-600→label-brand · bg-orange-100→bg-brand-tint · bg-orange-50→bg-brand-tint-soft · text-black→label-black-strong |
| 6 | 차트 dsColor() → 런타임 getComputedStyle + hereby:theme-change | 경영 대시보드 47곳 | canvas 는 var() 불가 |
| 7 | 네이티브 Light 고정 해제(android styles.xml · iOS UIUserInterfaceStyle) | 직원 앱 | 다크 출시 PR 에서 |
| 8 | --info 파랑 | 적용됨(2026-10-13) | D5 |
| 9 | color.md 「고르는 순서」 시멘틱 1순위로 | 문서 | 이번에 반영 |
Playground — 보류 결정의 대안
브랜드 색을 그대로 두고 다크에서도 흰 글자·아이콘으로 통일하기로 했다(짙은 색 버튼 공통 규칙). 대안 A·B 는 고르지 않았다.
3.18 — 글자 4.5 미달을 브랜드 유지로 수용
4.56 ✓ — 브랜드 색이 버튼에서 달라진다
5.36 ✓ — 「짙은 버튼은 흰 글자」 규칙과 어긋난다
/* D6 · 라이트 기존 대비 미달 (결정 완료) — 주요 버튼 — orange-500 + 흰 글자 유지 (3.18, 양 테마) */
/* 「현재」 는 정본 그대로. 아래는 고르지 않은 대안·이전 값을 미리보기 안에서 덮어쓴 선언. */
/* A · orange-600 채움 (안 고름) */
:root {
--color-action-primary: var(--color-orange-600);
}
[data-theme="dark"] {
--color-action-primary: var(--color-orange-600);
}
/* B · 어두운 글자 (안 고름) */
:root {
--color-action-primary-fg: var(--color-neutral-900);
}
[data-theme="dark"] {
--color-action-primary-fg: var(--color-neutral-900);
}무엇을 고르는가
2026-10-13 에 전부 결정돼 정본에 반영됐어요(아래 표). Playground 의 「현재」 열이 지금 정본이고, 나머지 열은 고르지 않은 대안(또는 이전 값)을 미리보기 안에서만 덮어쓴 기록이에요 — 되돌리거나 다시 고를 때 참고합니다.
| 결정 | 현재 | 대안 | 바뀌는 곳 |
|---|---|---|---|
| D7 다크 경계 3:1 | 적용 · A 새 단계 neutral-450 #707070(실측 3.10): 다크 line-normal · 카드 · strong 경계 | 다크만 | |
| D6 주요 버튼 3.18 | orange-500 + 흰 글자 | 유지 — 브랜드 색 그대로, 다크에서도 흰 글자(3.18 양 테마). 대안 A orange-600(4.56) · B 어두운 글자(5.36)는 안 고름 | — |
| D6 연한 주요 4.16 | orange-600 글자 | 유지(대안 orange-700 5.92 안 고름) | — |
| D6 상태 잉크 글자 | 600 / 400 / 600 | 유지 — 글자는 틴트 쌍(notice·라벨)으로 | — |
| D6 notice 글자 | 700(3.93 · 4.42) | 유지 700 | — |
| D6 입력칸 테두리 1.26 | 적용 — 새 단계 neutral-450(흰 배경 4.99 · 다크 3.10)을 --color-line-field 로 입력칸·셀렉트·라인 버튼 경계에. 카드·구분선(line-normal)은 그대로. 2026-10-20 재결정: 라이트 입력칸 · 셀렉트(--ds-textfield-border) · 라인 버튼 경계(--color-action-outline-border)를 neutral-100 으로 되돌림 — 450 이 너무 진하게 읽혔고 입력칸은 hover(300)보다 진한 역전도 있었다. 다크 line-field 도 450 → 500(카드 면 위 3.10 → 2.36) — 너무 밝게 읽혔다. 3:1 기준은 이 두 컨트롤에서 내려놓았다 | 입력칸 · 라인 버튼 — 라이트 시각 변화 | |
| D6 label-muted 3.23 | neutral-400 | 유지 — 단 아이콘 버튼 muted 의 잉크만 --color-action-icon-muted(500 · 다크 400)로 진하게(적용). 2026-10-19 재결정: 다크 값만 500 → 400(container 위 2.36 → 4.7, 직원 앱 다크 점검에서 보조 문구 · 비활성 탭 · 날짜가 읽히지 않음). 라이트는 그대로 | 아이콘 버튼 muted(알림벨 등) — 라이트 시각 변화 |
| D5 info 파랑 | 적용 · A blue-50/200/700, 아이콘 500 · 다크 900/700/200, 아이콘 300(실측 8.04 · 9.16) — 10/19 노랑으로 되돌렸다가 같은 날 사용자 확인으로 파랑 유지 | notice info 189곳 · 바 notice info — 라이트 시각 변화 |