# Color 팔레트와 역할 토큰 두 층으로 나뉜 색상 체계입니다. > 원본: `03_ASSET_GUIDE/foundations/color.js` · 값: `03_ASSET_GUIDE/styles/tokens.css` > status `stable` · specSource `measured` | 단계 | 역할 | 예시 | |---|---|---| | **Primitive** (팔레트) | 순수한 색 값 — "이 빨강" | `--atom-red-600` = `#e5484d` | | **Semantic** (역할 토큰) | 그 색이 하는 일 — "위험을 뜻하는 전경색" | `--atom-fg-critical` → `var(--atom-red-600)` | > **원칙:** 화면을 만들 때 쓰는 것은 **역할 토큰뿐**입니다. 팔레트는 역할 토큰을 정의할 때만 씁니다. > 색상 변경이 필요한 경우, 역할 토큰 값을 변경하면 모든 컴포넌트에 적용됩니다. ```css --atom-red-600: #e5484d; /* 팔레트 — "이 빨강" */ --atom-fg-critical: var(--atom-red-600); /* 역할 — "위험을 뜻하는 전경색" */ ``` --- ## 1. Primitive tokens 팔레트는 계열별로 3–4단계만 둡니다(gray 만 12단계). 쓰이는 곳이 없으면 단계를 만들지 않습니다. 다크 모드에서는 **팔레트 값 자체가 바뀝니다**(아래 Dark 열). ### gray — 중립 · 12단계 | Token | Light | Dark | 비고 | |---|---|---|---| | `--atom-gray-00` | `#ffffff` | `#161618` | 순백 | | `--atom-gray-50` | `#f9f9fa` | `#1a1b1f` | | | `--atom-gray-100` | `#f3f3f5` | `#1d1e25` | | | `--atom-gray-200` | `#ebecef` | `#24252b` | | | `--atom-gray-300` | `#dcdde3` | `#2f3038` | | | `--atom-gray-400` | `#c0c2cc` | `#4a4c56` | | | `--atom-gray-500` | `#9fa1ad` | `#6f717d` | | | `--atom-gray-600` | `#7a7c8b` | `#9697a0` | | | `--atom-gray-700` | `#55586d` | `#b4b5bc` | | | `--atom-gray-800` | `#33343d` | `#dcdde3` | | | `--atom-gray-900` | `#1a1b20` | `#eaeaec` | | | `--atom-gray-1000` | `#0f1014` | `#f3f3f5` | | ### primary — 브랜드 · 6단계 (기준은 primary-600) | Token | Light | Dark | 비고 | |---|---|---|---| | `--atom-primary-50` | `#f1f1fb` | `#101232` | | | `--atom-primary-100` | `#dadcf6` | `#161945` | | | `--atom-primary-200` | `#c2c5f0` | `#202564` | | | `--atom-primary-500` | `#5d64cb` | `#868cea` | | | `--atom-primary-600` | `#383fae` | `#4750d1` | **브랜드** | | `--atom-primary-700` | `#2d338b` | `#9da2f1` | | ### 의미 계열 `blue` 는 의도적으로 cyan 쪽(약 194°)에 있습니다. `fg-informative` 를 만드는 색인데, 브랜드의 236° 와 25° 밖에 떨어지지 않으면 정보 아이콘이 브랜드 강조로 읽혔기 때문입니다. | Token | Light | Dark | 계열 | |---|---|---|---| | `--atom-blue-50` | `#f1f9fb` | `#0e282f` | 정보 | | `--atom-blue-100` | `#d5eef6` | `#153742` | 정보 | | `--atom-blue-600` | `#0e7290` | `#51c7ec` | 정보 | | `--atom-green-50` | `#e9f8ef` | `#0d2118` | 성공 | | `--atom-green-100` | `#d1f0dd` | `#123222` | 성공 | | `--atom-green-500` | `#12b76a` | `#34d391` | 성공 | | `--atom-green-700` | `#1a7f4b` | `#5fdca7` | 성공 | | `--atom-red-50` | `#fdeded` | `#2b1315` | 위험 | | `--atom-red-100` | `#fbdcdc` | `#3d1a1c` | 위험 | | `--atom-red-600` | `#e5484d` | `#ff6369` | 위험 | | `--atom-red-700` | `#c62b31` | `#ff8d91` | 위험 | | `--atom-yellow-50` | `#fff8e6` | `#241a05` | 주의 | | `--atom-yellow-100` | `#ffefc4` | `#35270a` | 주의 | | `--atom-yellow-700` | `#9a6800` | `#e0b44b` | 주의 | --- ## 2. Semantic tokens ### 역할 토큰의 문법 역할 토큰은 `--atom-{속성}-{역할}-{변형}` 순서입니다. | 자리 | 값 | 뜻 | |---|---|---| | 속성 | `bg` · `fg` · `border` | 배경 · 전경(글자·아이콘) · 경계선 | | 역할 | `neutral` · `brand` · `critical` · `positive` · `informative` · `warning` | 색이 전하는 의미 | | 변형 | `solid` · `weak` · `contrast` · `subtle` · `muted` | 같은 역할 안의 강조도 | `--atom-bg-critical-weak` 은 "위험을 뜻하는, 강조도가 낮은 배경" 입니다. 변형은 강조도 순서입니다. | 변형 | 강조도 | 쓰임 | |---|---|---| | `solid` | 가장 높음 | 색으로 채운 면. 그 위에는 `fg-on-solid` 를 얹는다 | | `weak` | 낮음 | 옅게 깐 배경. 그 위에는 같은 역할의 `fg-*-contrast` 를 얹는다 | | (없음) | 기본 | `--atom-fg-critical` 처럼 기본 전경색 | | `contrast` | 대비 강화 | `weak` 배경 위에서 읽히도록 어둡게 만든 전경 | | `muted` · `subtle` | 가장 낮음 | 보조 정보, 비활성 | ### 표면 (bg) 배경은 역할과 별개로 **층위**를 가집니다. 라이트 모드에서는 canvas · layer · layer-raised 세 값이 모두 흰색이라 층위가 그림자로만 표현됩니다. 다크 모드에서는 위에 있는 층일수록 밝습니다. 층위 토큰은 **큰 영역에만** 씁니다. 컴포넌트 배경은 `--atom-bg-neutral-weak` 같은 역할 토큰을 씁니다. 층위와 그림자의 관계는 [Shadow](./shadow.md) 를 참고하세요. | Token | Value | Light | Dark | 쓰임 | |---|---|---|---|---| | `--atom-bg-canvas` | `{gray-00}` | `#ffffff` | `#161618` | 페이지 바닥 | | `--atom-bg-layer` | `{gray-00}` | `#ffffff` | `#26272e` | 카드·팝오버 표면 | | `--atom-bg-layer-raised` | `{gray-00}` | `#ffffff` | `#2f3038` | 그 위에 또 뜬 표면 | | `--atom-bg-neutral-weak` | `{gray-100}` | `#f3f3f5` | `#2f3038` | 옅은 중립 배경 | | `--atom-bg-neutral-solid` | `{gray-900}` | `#1a1b20` | `#f3f3f5` | 반전 배경 | | `--atom-bg-placeholder` | `{gray-100}` | `#f3f3f5` | `#1d1e25` | 이미지 자리 표시 | | `--atom-bg-scrim` | — | `rgba(0,0,0,0.5)` | `rgba(0,0,0,0.6)` | 모달 뒤 딤 | | `--atom-bg-tooltip` | — | `#1a1b20` | `#3a3b45` | 툴팁 칩 | | `--atom-bg-stage` | `{gray-200}` | `#ebecef` | `#24252b` | 문서 데모 무대 | | `--atom-bg-code` | `{gray-50}` | `#f9f9fa` | `#1a1b1f` | 코드 블록 | 다크 값에서 canvas · layer · layer-raised · neutral-weak · neutral-solid · placeholder 는 팔레트 참조가 아니라 직접 값으로 다시 선언됩니다(`tokens.css` 다크 블록). `--atom-bg-layer` 는 다크에서 `--atom-bg-placeholder` 보다 밝게 유지됩니다. 프리뷰 표면이 썸네일 틀 *위에* 놓인 카드로 읽히도록, 라이트와 같은 관계를 지키기 위해서입니다. `--atom-bg-stage` 는 `--atom-bg-neutral-weak` 보다 한 단계 진합니다. `weak` 변형이나 읽기 전용 입력을 무대 위에 올렸을 때 배경과 구분되도록 하기 위해서입니다. `stage` · `code` 는 문서 사이트용 표면입니다. ### 전경 (fg) | Token | Value | Light | Dark | 흰 배경 대비 / 쓰임 | |---|---|---|---|---| | `--atom-fg-neutral` | — | `#1a1b20` | `#eaeaec` | 17.19:1 | | `--atom-fg-neutral-muted` | — | `#55586d` | `#9697a0` | 6.99:1 | | `--atom-fg-neutral-subtle` | `{gray-500}` | `#9fa1ad` | `#6f717d` | 2.57:1 — 읽어야 하는 글자에 쓸 수 없음 | | `--atom-fg-side-item` | — | `#55586d` | `#b4b5bc` | 6.99:1 | | `--atom-fg-brand` | `{primary-600}` (다크 `{primary-700}`) | `#383fae` | `#9da2f1` | 8.43:1 | | `--atom-fg-on-solid` | `{gray-00}` | `#ffffff` | `#161618` | solid 면 위 전경 | | `--atom-fg-tooltip` | — | `#ffffff` | `#f3f3f5` | 툴팁 칩 위 전경 | | `--atom-fg-placeholder-icon` | `{gray-300}` | `#dcdde3` | `#3a3b45` | 장식 전용 | ### 경계선 (border) | Token | Value | Light | Dark | 흰 배경 대비 | |---|---|---|---|---| | `--atom-border-subtle` | `{gray-200}` | `#ebecef` | `#24252b` | 1.18:1 | | `--atom-border-default` | `{gray-300}` | `#dcdde3` | `#2f3038` | 1.36:1 | | `--atom-border-strong` | `{gray-900}` | `#1a1b20` | `#eaeaec` | 17.19:1 | `--atom-border-strong` 은 선택 상태(Radio Card 의 `[data-state="checked"]`)에서만 씁니다. 컨트롤의 포커스 링은 `--atom-primary-600` 입니다(`--atom-control-border-focus`). ### 컨트롤 테두리 (control surface) `tokens.css` 의 `--- Control surface ---` 절. Field 의 Control 슬롯이 담는 상자(Input · Textarea · Select · Multi Select · Date Input · Time Input · Input Button)가 공유하는 테두리입니다. 모든 컨트롤이 테두리를 inset 그림자로 그리므로, 폭과 색이 아니라 **`box-shadow` 값 전체**가 토큰입니다. 한 토큰이 선언 전체를 바꾸므로 상태끼리 어긋나지 않습니다. 높이 토큰(`--atom-control-h*`)은 [Spacing](./spacing.md#레이아웃-고정값) 에 있습니다. | Token | Value | 쓰임 | |---|---|---| | `--atom-control-border` | `inset 0 0 0 1px var(--atom-border-default)` | 기본 1px. Button 과 같은 폭 | | `--atom-control-border-hover` | `inset 0 0 0 1px var(--atom-fg-neutral-subtle)` | hover — 폭은 그대로, 색만 진하게 | | `--atom-control-border-focus` | `inset 0 0 0 2px var(--atom-primary-600)` | focus — 2px | | `--atom-control-border-invalid` | `inset 0 0 0 2px var(--atom-fg-critical)` | invalid — 2px | 상태 링이 2px 인 이유: Button 자체 포커스 링도 2px 이고, 상태는 테두리보다 멀리서도 읽혀야 하기 때문입니다. ### 상태 (status) | Token | Value | Light | Dark | |---|---|---|---| | `--atom-bg-critical-solid` | `{red-600}` (다크는 직접 값) | `#e5484d` | `#c8353a` | | `--atom-bg-critical-weak` | `{red-50}` | `#fdeded` | `#2b1315` | | `--atom-fg-critical` | `{red-600}` | `#e5484d` | `#ff6369` | | `--atom-fg-critical-contrast` | `{red-700}` | `#c62b31` | `#ff8d91` | | `--atom-bg-positive-weak` | `{green-50}` | `#e9f8ef` | `#0d2118` | | `--atom-fg-positive` | `{green-500}` | `#12b76a` | `#34d391` | | `--atom-fg-positive-contrast` | `{green-700}` | `#1a7f4b` | `#5fdca7` | | `--atom-bg-informative-weak` | `{blue-50}` | `#f1f9fb` | `#0e282f` | | `--atom-fg-informative` | `{blue-600}` | `#0e7290` | `#51c7ec` | | `--atom-bg-warning-weak` | `{yellow-50}` | `#fff8e6` | `#241a05` | | `--atom-fg-warning` | `{yellow-700}` | `#9a6800` | `#e0b44b` | > [!NOTE] > **`positive` · `informative` · `warning` 에는 `solid` 가 없습니다** > `critical` 만 `bg-critical-solid` 를 가집니다. 색으로 채운 성공·정보·주의 면이 아직 필요한 곳이 없기 때문입니다. > Toast 나 Inline Banner 를 만들면서 필요해지면 추가합니다. > 마찬가지로 `border-critical` · `border-brand` 같은 **의미를 가진 경계선 토큰도 없습니다.** ### 차트 시리즈 (chart) 위의 램프는 UI 상태용이라 시리즈 구분에 쓸 수 있는 색상 계열이 셋뿐이고, 초록·빨강·노랑은 상태 색으로 쓰입니다. 상태 색은 시리즈 색으로 쓰지 않습니다. 시리즈 색은 팔레트와 별도로 고른 색이며, 슬롯 1 은 `--atom-primary-600` 그대로여서 시리즈가 하나인 차트는 브랜드 색으로 그려집니다. > [!NOTE] > **임시 값입니다** > 아래 여섯 색은 확정되지 않은 임의의 값입니다. 디자인이 확정되면 바뀔 수 있습니다. | Token | 이름 | Light | Dark | 흰 배경 대비 | |---|---|---|---|---| | `--atom-chart-series-1` | indigo | `#383fae` | `#8069e2` | 8.43:1 | | `--atom-chart-series-2` | orange | `#eb6834` | `#e0703c` | 3.20:1 | | `--atom-chart-series-3` | teal | `#0b8fb5` | `#0fa5bd` | 3.74:1 | | `--atom-chart-series-4` | yellow | `#eda100` | `#bd8708` | 2.17:1 | | `--atom-chart-series-5` | magenta | `#c2379b` | `#cc4fa4` | 4.87:1 | | `--atom-chart-series-6` | green | `#1a7f4b` | `#22a866` | 5.02:1 | | `--atom-chart-on-fill-ink` | — | `#1a1b20` | `#161618` | 시리즈 면 위 라벨 잉크 | - **순서가 색각 안전 장치입니다.** 라이트 표면(`#ffffff`) 기준 인접 쌍 최악 CVD ΔE 10.5(deutan), 정상 시각 ΔE 31.1. 재정렬하거나 7번째를 추가하려면 검증을 다시 돌려야 합니다. - 슬롯 4 는 2.17:1 로 3:1 미만입니다(의도). 그래서 보이는 라벨 또는 표 뷰를 반드시 함께 둡니다 — 색만으로 두지 않습니다. - 다크 값은 같은 여섯 색상을 **다크 표면(`#26272e`)에 맞춰 다시 스텝한 값**입니다(뒤집은 값이 아님). 인접 최악 CVD ΔE 9.7(deutan) / 8.4(tritan), 정상 시각 ΔE 22.9, 여섯 모두 3:1 이상. - `--atom-chart-on-fill-ink`: 슬롯 2·3·4 는 흰 라벨 대비가 3.20 / 3.74 / 2.17 이라 11px 라벨에 부족하고, 잉크는 같은 면에서 5.37 / 4.59 / 7.94 입니다. 그 슬롯들은 이 잉크를 쓰고, 1·5·6 은 `--atom-fg-on-solid` (8.43 / 4.87 / 5.02)를 씁니다. 다크에서는 모든 슬롯이 잉크를 쓰며, 그때 `--atom-fg-on-solid` 가 이미 잉크 값입니다. ### 오버레이 (알파) 오버레이는 **브랜드 색에 알파를 준 값**입니다. 선택된 항목이 현재 위치로 읽히도록 브랜드 색을 썼습니다. 색이 섞이면 같은 불투명도에서도 약해 보이므로 중립 버전보다 알파를 높게 잡았습니다. | Token | Light | Dark | 쓰임 | |---|---|---|---| | `--atom-bg-nav-active` | `rgba(56,63,174,0.05)` | `rgba(157,162,241,0.085)` | 헤더 현재 항목 | | `--atom-bg-nav-hover` | `rgba(56,63,174,0.038)` | `rgba(157,162,241,0.06)` | 헤더 hover | | `--atom-bg-side-active` | `rgba(56,63,174,0.09)` | `rgba(157,162,241,0.15)` | 사이드바 현재 항목 | | `--atom-bg-side-hover` | `rgba(56,63,174,0.053)` | `rgba(157,162,241,0.095)` | 사이드바 hover | | `--atom-skeleton-shimmer` | `rgba(255,255,255,0.6)` | `rgba(255,255,255,0.07)` | 스켈레톤 | ### 규칙 1. **화면에는 역할 토큰만** — 팔레트는 역할 토큰을 정의할 때만 씁니다. 2. **`weak` 배경에는 `contrast` 전경을 짝지어** 씁니다. | 배경 | 짝이 되는 전경 | 라이트 대비 | |---|---|---| | `--atom-bg-critical-weak` | `--atom-fg-critical-contrast` | 4.88:1 | | `--atom-bg-positive-weak` | `--atom-fg-positive-contrast` | 4.58:1 | | `--atom-bg-informative-weak` | `--atom-fg-informative` | 5.14:1 | | `--atom-bg-warning-weak` | `--atom-fg-warning` | 4.55:1 | | `--atom-bg-*-solid` | `--atom-fg-on-solid` | — | 3. **상태 색과 시리즈 색을 섞지 않습니다** — 시리즈는 `--atom-chart-series-*`, 상태는 `--atom-fg-critical` 계열. 4. **알파는 딤(`--atom-bg-scrim`), 내비게이션 오버레이, 스켈레톤 시머에만** 씁니다. 5. **쓰이지 않는 토큰은 만들지 않습니다** — 필요할 때 추가합니다. --- ## 3. Design tokens (JSON) [W3C Design Tokens (DTCG)](https://tr.designtokens.org/format/) 형식입니다. `{...}`는 다른 토큰을 참조합니다. 경로는 CSS 변수에서 `--atom-` 를 뗀 이름을 그룹으로 묶은 것입니다(`--atom-bg-critical-weak` → `color.bg.critical-weak`). `$value` 는 라이트 값, `$extensions.atom.dark` 는 다크 값입니다(값이 참조로 해결되는 토큰은 다크를 따로 적지 않음). ```json { "color": { "$type": "color", "gray": { "00": { "$value": "#ffffff", "$extensions": { "atom": { "dark": "#161618" } } }, "50": { "$value": "#f9f9fa", "$extensions": { "atom": { "dark": "#1a1b1f" } } }, "100": { "$value": "#f3f3f5", "$extensions": { "atom": { "dark": "#1d1e25" } } }, "200": { "$value": "#ebecef", "$extensions": { "atom": { "dark": "#24252b" } } }, "300": { "$value": "#dcdde3", "$extensions": { "atom": { "dark": "#2f3038" } } }, "400": { "$value": "#c0c2cc", "$extensions": { "atom": { "dark": "#4a4c56" } } }, "500": { "$value": "#9fa1ad", "$extensions": { "atom": { "dark": "#6f717d" } } }, "600": { "$value": "#7a7c8b", "$extensions": { "atom": { "dark": "#9697a0" } } }, "700": { "$value": "#55586d", "$extensions": { "atom": { "dark": "#b4b5bc" } } }, "800": { "$value": "#33343d", "$extensions": { "atom": { "dark": "#dcdde3" } } }, "900": { "$value": "#1a1b20", "$extensions": { "atom": { "dark": "#eaeaec" } } }, "1000": { "$value": "#0f1014", "$extensions": { "atom": { "dark": "#f3f3f5" } } } }, "primary": { "50": { "$value": "#f1f1fb", "$extensions": { "atom": { "dark": "#101232" } } }, "100": { "$value": "#dadcf6", "$extensions": { "atom": { "dark": "#161945" } } }, "200": { "$value": "#c2c5f0", "$extensions": { "atom": { "dark": "#202564" } } }, "500": { "$value": "#5d64cb", "$extensions": { "atom": { "dark": "#868cea" } } }, "600": { "$value": "#383fae", "$extensions": { "atom": { "dark": "#4750d1" } } }, "700": { "$value": "#2d338b", "$extensions": { "atom": { "dark": "#9da2f1" } } } }, "blue": { "50": { "$value": "#f1f9fb", "$extensions": { "atom": { "dark": "#0e282f" } } }, "100": { "$value": "#d5eef6", "$extensions": { "atom": { "dark": "#153742" } } }, "600": { "$value": "#0e7290", "$extensions": { "atom": { "dark": "#51c7ec" } } } }, "green": { "50": { "$value": "#e9f8ef", "$extensions": { "atom": { "dark": "#0d2118" } } }, "100": { "$value": "#d1f0dd", "$extensions": { "atom": { "dark": "#123222" } } }, "500": { "$value": "#12b76a", "$extensions": { "atom": { "dark": "#34d391" } } }, "700": { "$value": "#1a7f4b", "$extensions": { "atom": { "dark": "#5fdca7" } } } }, "red": { "50": { "$value": "#fdeded", "$extensions": { "atom": { "dark": "#2b1315" } } }, "100": { "$value": "#fbdcdc", "$extensions": { "atom": { "dark": "#3d1a1c" } } }, "600": { "$value": "#e5484d", "$extensions": { "atom": { "dark": "#ff6369" } } }, "700": { "$value": "#c62b31", "$extensions": { "atom": { "dark": "#ff8d91" } } } }, "yellow": { "50": { "$value": "#fff8e6", "$extensions": { "atom": { "dark": "#241a05" } } }, "100": { "$value": "#ffefc4", "$extensions": { "atom": { "dark": "#35270a" } } }, "700": { "$value": "#9a6800", "$extensions": { "atom": { "dark": "#e0b44b" } } } }, "bg": { "canvas": { "$value": "{color.gray.00}", "$extensions": { "atom": { "dark": "#161618" } } }, "layer": { "$value": "{color.gray.00}", "$extensions": { "atom": { "dark": "#26272e" } } }, "layer-raised": { "$value": "{color.gray.00}", "$extensions": { "atom": { "dark": "#2f3038" } } }, "neutral-weak": { "$value": "{color.gray.100}", "$extensions": { "atom": { "dark": "#2f3038" } } }, "neutral-solid": { "$value": "{color.gray.900}", "$extensions": { "atom": { "dark": "#f3f3f5" } } }, "placeholder": { "$value": "{color.gray.100}", "$extensions": { "atom": { "dark": "#1d1e25" } } }, "scrim": { "$value": "rgba(0, 0, 0, 0.5)", "$extensions": { "atom": { "dark": "rgba(0, 0, 0, 0.6)" } } }, "tooltip": { "$value": "#1a1b20", "$extensions": { "atom": { "dark": "#3a3b45" } } }, "nav-active": { "$value": "rgba(56, 63, 174, 0.05)", "$extensions": { "atom": { "dark": "rgba(157, 162, 241, 0.085)" } } }, "nav-hover": { "$value": "rgba(56, 63, 174, 0.038)", "$extensions": { "atom": { "dark": "rgba(157, 162, 241, 0.06)" } } }, "side-active": { "$value": "rgba(56, 63, 174, 0.09)", "$extensions": { "atom": { "dark": "rgba(157, 162, 241, 0.15)" } } }, "side-hover": { "$value": "rgba(56, 63, 174, 0.053)", "$extensions": { "atom": { "dark": "rgba(157, 162, 241, 0.095)" } } }, "critical-solid": { "$value": "{color.red.600}", "$extensions": { "atom": { "dark": "#c8353a" } } }, "critical-weak": { "$value": "{color.red.50}" }, "positive-weak": { "$value": "{color.green.50}" }, "informative-weak": { "$value": "{color.blue.50}" }, "warning-weak": { "$value": "{color.yellow.50}" }, "stage": { "$value": "{color.gray.200}" }, "code": { "$value": "{color.gray.50}" } }, "fg": { "neutral": { "$value": "#1a1b20", "$extensions": { "atom": { "dark": "#eaeaec" } } }, "neutral-muted": { "$value": "#55586d", "$extensions": { "atom": { "dark": "#9697a0" } } }, "neutral-subtle": { "$value": "{color.gray.500}", "$extensions": { "atom": { "dark": "#6f717d" } } }, "side-item": { "$value": "#55586d", "$extensions": { "atom": { "dark": "#b4b5bc" } } }, "on-solid": { "$value": "{color.gray.00}", "$extensions": { "atom": { "dark": "#161618" } } }, "brand": { "$value": "{color.primary.600}", "$extensions": { "atom": { "dark": "{color.primary.700}" } } }, "placeholder-icon": { "$value": "{color.gray.300}", "$extensions": { "atom": { "dark": "#3a3b45" } } }, "tooltip": { "$value": "#ffffff", "$extensions": { "atom": { "dark": "#f3f3f5" } } }, "critical": { "$value": "{color.red.600}" }, "critical-contrast": { "$value": "{color.red.700}" }, "positive": { "$value": "{color.green.500}" }, "positive-contrast": { "$value": "{color.green.700}" }, "informative": { "$value": "{color.blue.600}" }, "warning": { "$value": "{color.yellow.700}" } }, "border": { "subtle": { "$value": "{color.gray.200}", "$extensions": { "atom": { "dark": "#24252b" } } }, "default": { "$value": "{color.gray.300}", "$extensions": { "atom": { "dark": "#2f3038" } } }, "strong": { "$value": "{color.gray.900}", "$extensions": { "atom": { "dark": "#eaeaec" } } } }, "skeleton-shimmer": { "$value": "rgba(255, 255, 255, 0.6)", "$extensions": { "atom": { "dark": "rgba(255, 255, 255, 0.07)" } } }, "chart": { "series-1": { "$value": "#383fae", "$extensions": { "atom": { "dark": "#8069e2" } } }, "series-2": { "$value": "#eb6834", "$extensions": { "atom": { "dark": "#e0703c" } } }, "series-3": { "$value": "#0b8fb5", "$extensions": { "atom": { "dark": "#0fa5bd" } } }, "series-4": { "$value": "#eda100", "$extensions": { "atom": { "dark": "#bd8708" } } }, "series-5": { "$value": "#c2379b", "$extensions": { "atom": { "dark": "#cc4fa4" } } }, "series-6": { "$value": "#1a7f4b", "$extensions": { "atom": { "dark": "#22a866" } } }, "on-fill-ink": { "$value": "#1a1b20", "$extensions": { "atom": { "dark": "#161618" } } } } }, "control": { "border": { "$type": "shadow", "$value": { "inset": true, "offsetX": "0px", "offsetY": "0px", "blur": "0px", "spread": "1px", "color": "{color.border.default}" } }, "border-hover": { "$type": "shadow", "$value": { "inset": true, "offsetX": "0px", "offsetY": "0px", "blur": "0px", "spread": "1px", "color": "{color.fg.neutral-subtle}" } }, "border-focus": { "$type": "shadow", "$value": { "inset": true, "offsetX": "0px", "offsetY": "0px", "blur": "0px", "spread": "2px", "color": "{color.primary.600}" } }, "border-invalid": { "$type": "shadow", "$value": { "inset": true, "offsetX": "0px", "offsetY": "0px", "blur": "0px", "spread": "2px", "color": "{color.fg.critical}" } } } } ``` --- ## 4. CSS 적용 모든 토큰은 `styles/tokens.css` 의 CSS 변수(`--atom-*`)로 씁니다. Tailwind 매핑은 없습니다. ```css /* weak 배경에는 contrast 전경 */ .atom-callout--tone_critical { background: var(--atom-bg-critical-weak); color: var(--atom-fg-critical-contrast); } /* solid 면에는 on-solid */ .atom-button--variant_critical-solid { background: var(--atom-bg-critical-solid); color: var(--atom-fg-on-solid); } ``` ### 다크 모드 값의 위치 `tokens.css` 는 다크 값을 **두 블록에 같은 내용으로** 씁니다. 1. `:root` — 라이트 값 전부 (기본) 2. `@media (prefers-color-scheme: dark) { :root:not([data-theme="light"]) }` — 시스템 설정 3. `:root[data-theme="dark"]` — 사용자가 직접 고른 값 2번만 있으면 시스템이 라이트인데 사용자가 다크를 고른 경우가 깨지고, 3번만 있으면 시스템 설정을 따르지 못합니다. `:not([data-theme="light"])` 가드는 시스템이 다크여도 사용자가 라이트를 명시했으면 라이트가 이기게 합니다. **어떤 색도 미디어 쿼리 안에서만 정의하지 않습니다.** 그렇게 정의한 색은 라이트 모드에서 값이 없습니다. 대부분의 역할 토큰은 팔레트를 참조하므로 **한 번만 선언하면 두 모드가 모두 해결**됩니다. 다크 블록에서 다시 쓰는 것은 팔레트와 아래 예외뿐입니다. | 예외 | 이유 | |---|---| | `--atom-bg-tooltip` · `--atom-fg-tooltip` | 툴팁은 두 모드 모두 어두운 칩. 다크에서는 카드보다 위로 떠야 해서 역전된 중립색을 재사용할 수 없다 | | `--atom-bg-critical-solid` | 다크의 `red-600` 은 글자용으로 밝혀 놓아서, 흰 라벨을 얹는 면으로 쓰기엔 밝다. 별도로 `#c8353a` 를 준다 | | `--atom-fg-brand` | 라이트는 `primary-600`, 다크는 `primary-700`. 다크 팔레트에서는 700 이 600 보다 밝다 | --- ## 5. 사용 가이드 ### 화면에는 역할 토큰만 씁니다 팔레트는 역할 토큰을 정의할 때만 씁니다. 컴포넌트 CSS 에서 `--atom-gray-500` 을 직접 쓰면 그 색의 용도가 코드에 남지 않습니다. - ✅ **Do** `color: var(--atom-fg-neutral-muted)` 처럼 역할로 씁니다. - ❌ **Don't** `color: var(--atom-gray-600)` 처럼 팔레트를 직접 부르지 않습니다. - ❌ **Don't** `color: #55586d` 처럼 값을 직접 쓰지 않습니다. ### 색만으로 의미를 전하지 않습니다 색각 이상 사용자는 빨강과 초록을 구분하기 어렵습니다. 오류를 빨간 테두리로만 표시하면 오류를 알아볼 수 없습니다. - ✅ **Do** 빨간 테두리 + 아이콘 + "이메일 형식이 아닙니다" 문구를 함께 보여줍니다. - ❌ **Don't** 색 변화만으로 오류·성공을 표시하지 않습니다. ### 브랜드 색은 아껴 씁니다 `--atom-fg-brand` 와 `primary-600` 은 한 화면에 한 요소에만 쓰는 것이 기본입니다. 여러 곳에 쓰면 강조 효과가 약해집니다. - ✅ **Do** 화면의 목표가 되는 액션 하나에만 브랜드 색을 씁니다. - ❌ **Don't** 링크·아이콘·배지에 모두 브랜드 색을 쓰지 않습니다. ### 역할의 의미를 바꾸지 않습니다 `critical` 은 되돌리기 어려운 일에만 씁니다. 강조 목적으로 빨강을 쓰면 실제 위험 액션의 경고 효과가 약해집니다. - ✅ **Do** 삭제·탈퇴에 `critical`, 저장 완료에 `positive` 를 씁니다. - ❌ **Don't** 이벤트 배너를 눈에 띄게 하려고 `critical` 을 쓰지 않습니다. ### `weak` 배경에는 `contrast` 전경을 짝지어 씁니다 `--atom-bg-critical-weak` 위에 `--atom-fg-critical` 을 얹으면 대비가 부족합니다. 옅은 배경용으로 어둡게 만들어 둔 `--atom-fg-critical-contrast` 가 그 짝입니다(짝 표는 2절 규칙 참고). ### 투명도로 새 색을 만들지 않습니다 `opacity` 나 알파를 얹어 색을 만들면 배경에 따라 결과가 달라집니다. 알파는 딤(`--atom-bg-scrim`), 내비게이션 오버레이, 스켈레톤 시머에만 씁니다. - ✅ **Do** 옅은 배경이 필요하면 `--atom-bg-*-weak` 토큰을 씁니다. - ❌ **Don't** `opacity: 0.5` 로 비활성 색을 만들지 않습니다. ### 상태 색을 시리즈 색으로 쓰지 않습니다 `--atom-fg-positive` · `--atom-fg-critical` · `--atom-fg-warning` 은 **뜻이 정해진 색**입니다. 차트 시리즈에 쓰면 그 시리즈가 좋거나 나쁜 값으로 읽힙니다. 마찬가지로 시리즈 색으로 좋고 나쁨을 표시하지 않습니다. - ✅ **Do** 시리즈는 `--atom-chart-series-*`, 상태는 `--atom-fg-critical` 계열. - ❌ **Don't** 시리즈가 넷이라서 초록을 끌어오지 않습니다. - ⚠️ **Caution** 같은 색상 계열이 나란히 놓이면 아이콘과 라벨을 함께 답니다. 색상만으로는 부족합니다. ### 쓰이지 않는 토큰은 만들지 않습니다 팔레트는 계열별로 3–4단계만 둡니다. 역할이 명확해도 쓰이는 곳이 없으면 토큰을 만들지 않고, 필요할 때 추가합니다.