# Shadow 표면이 떠 있는 높이를 나타내는 3단계 그림자입니다. > 원본: `03_ASSET_GUIDE/foundations/shadow.js` · 값: `03_ASSET_GUIDE/styles/tokens.css` (`--- Elevation ---` 절) > status `stable` · specSource `measured` | 단계 | 역할 | 예시 | |---|---|---| | **Primitive** | 높이 3단계 `--atom-shadow-s{n}` | `--atom-shadow-s2` = `0 2px 8px rgba(0,0,0,0.08)` | | **Semantic** | 없음 — 층위별 사용 규칙(아래 2절)으로 대신합니다 | 팝오버 → `s2` | > **원칙:** 그림자는 **실제로 떠 있는 표면에만** 씁니다. 층위가 같으면 그림자도 같습니다. --- ## 1. Primitive tokens 숫자가 커질수록 **표면이 화면에서 더 멀리 떠 있습니다.** 단계가 올라갈수록 오프셋·blur·투명도가 모두 커집니다. | Token | Light | Dark | 높이 | 대상 | |---|---|---|---|---| | `--atom-shadow-s1` | `0 1px 2px rgba(0, 0, 0, 0.06)` | `0 1px 2px rgba(0, 0, 0, 0.4)` | 바닥에 거의 붙음 | 카드 썸네일 (`.atom-pv-surface`) | | `--atom-shadow-s2` | `0 2px 8px rgba(0, 0, 0, 0.08)` | `0 2px 8px rgba(0, 0, 0, 0.5)` | 본문 위로 뜸 | Select 리스트박스, 디바이스 프레임 | | `--atom-shadow-s3` | `0 8px 24px rgba(0, 0, 0, 0.12)` | `0 8px 24px rgba(0, 0, 0, 0.6)` | 화면 전체 위 | 검색 다이얼로그, 모바일 사이드바 | 세 값 모두 **아래 방향 하나뿐**입니다. 오프셋이 `0 Npx` 라서 광원이 항상 화면 위쪽에 있다고 가정합니다. 화면 가장자리에 붙는 컴포넌트가 아직 없어서 Ant Design 처럼 상·하·좌·우 4방향을 두지 않았습니다. 하단 툴바나 좌측 드로어가 생기면 그때 방향을 늘립니다. ### 다크 모드에서는 알파를 올립니다 어두운 배경 위에서는 검은 그림자가 보이지 않습니다. ATOM 은 알파를 크게 올려 대응합니다. | Token | 라이트 알파 | 다크 알파 | |---|---|---| | `--atom-shadow-s1` | 0.06 | **0.4** | | `--atom-shadow-s2` | 0.08 | **0.5** | | `--atom-shadow-s3` | 0.12 | **0.6** | 오프셋과 blur 는 그대로 두고 알파만 바꿉니다. 그림자 크기가 높이를 나타내므로 모드가 바뀌어도 높이는 같아야 합니다. > [!NOTE] > 알파를 5배 이상 올려도 다크 모드에서 그림자는 라이트 모드만큼 뚜렷하지 않습니다. > 다크 모드에서 확실히 떠 보여야 하는 요소는 그림자 대신 `--atom-bg-layer-raised` 로 표면을 밝히거나 보더를 함께 씁니다. --- ## 2. Semantic tokens ATOM 은 그림자에 역할 토큰을 두지 않습니다. 높이는 **세 가지 방법을 함께** 써서 나타내며, 그림자는 그중 하나입니다. ### 높이를 나타내는 세 가지 방법 | 방법 | 토큰 | 언제 | |---|---|---| | **표면 색** | `--atom-bg-layer` · `--atom-bg-layer-raised` | 다크 모드에서 주된 수단. 뜰수록 밝아진다 | | **그림자** | `--atom-shadow-s1` … `s3` | 라이트 모드에서 주된 수단 | | **보더** | `--atom-border-subtle` | 그림자가 약할 때 경계를 대신 그린다 | ### 함께 쓰는 표면 토큰 | Token | Light | Dark | 역할 | |---|---|---|---| | `--atom-bg-layer` | `#ffffff` | `#26272e` | 떠 있는 표면의 기본 배경 | | `--atom-bg-layer-raised` | `#ffffff` | `#2f3038` | 더 높이 뜬 표면. 다크에서만 실제로 밝아진다 | | `--atom-border-subtle` | `#ebecef` | `#24252b` | 그림자를 보조하는 경계선 | | `--atom-bg-scrim` | `rgba(0,0,0,0.5)` | `rgba(0,0,0,0.6)` | 모달 뒤 딤 | 라이트 모드에서 `--atom-bg-layer` 와 `--atom-bg-layer-raised` 는 둘 다 흰색입니다. 흰 배경 위에서는 더 밝게 만들 수 없으므로 그림자가 층위를 전담하고, 다크에서만 두 값이 갈라져 표면 색이 층위를 나눕니다. 색 값은 [Color](./color.md) 에 있습니다. ### 저장소 내 사용처 | 위치 | 토큰 | 대상 | |---|---|---| | `styles/preview.css:60` | `s1` | `.atom-pv-surface` — 프리뷰 안의 흰 카드 | | `styles/doc.css:842` | `s2` | `.atom-demo-device` — 데모용 폰 프레임 | | `styles/elements/select.css:68` | `s2` | `.atom-select__listbox` | | `styles/chrome.css:626` | `s3` | 검색 다이얼로그 | | `styles/layout.css:131` | `s3` | 열린 상태의 모바일 사이드바 | ### 규칙 1. **떠 있는 표면에만** — 팝오버·다이얼로그처럼 실제로 위에 뜨는 것에만 씁니다. 2. **층위가 같으면 그림자도 같습니다** — 모든 팝오버에 `--atom-shadow-s2` 를 씁니다. 3. **상시 노출 요소에는 쓰지 않습니다** — 상시 영역은 `--atom-border-subtle` 로 경계를 그립니다. 예외는 **모바일 사이드바**(`layout.css`)로, 열려 있는 동안만 본문 위에 뜨므로 그때만 `--atom-shadow-s3` 가 붙습니다. 4. **그림자와 보더를 함께 쓰는 것을 기본으로** — 겹치는 표면에는 `inset` 보더를 함께 둡니다. --- ## 3. Design tokens (JSON) [W3C Design Tokens (DTCG)](https://tr.designtokens.org/format/) 형식입니다. 경로는 `--atom-shadow-s{n}` → `shadow.s{n}` 입니다. `$extensions.atom.dark` 는 다크 값입니다. ```json { "shadow": { "$type": "shadow", "s1": { "$value": { "offsetX": "0px", "offsetY": "1px", "blur": "2px", "spread": "0px", "color": "rgba(0, 0, 0, 0.06)" }, "$extensions": { "atom": { "dark": { "offsetX": "0px", "offsetY": "1px", "blur": "2px", "spread": "0px", "color": "rgba(0, 0, 0, 0.4)" } } } }, "s2": { "$value": { "offsetX": "0px", "offsetY": "2px", "blur": "8px", "spread": "0px", "color": "rgba(0, 0, 0, 0.08)" }, "$extensions": { "atom": { "dark": { "offsetX": "0px", "offsetY": "2px", "blur": "8px", "spread": "0px", "color": "rgba(0, 0, 0, 0.5)" } } } }, "s3": { "$value": { "offsetX": "0px", "offsetY": "8px", "blur": "24px", "spread": "0px", "color": "rgba(0, 0, 0, 0.12)" }, "$extensions": { "atom": { "dark": { "offsetX": "0px", "offsetY": "8px", "blur": "24px", "spread": "0px", "color": "rgba(0, 0, 0, 0.6)" } } } } } } ``` --- ## 4. CSS 적용 다크 값은 `@media (prefers-color-scheme: dark)` 와 `:root[data-theme="dark"]` 두 곳에 같은 값으로 정의됩니다. 한 곳에만 두면 사용자가 테마를 명시적으로 고른 경우가 깨집니다. ```css /* 팝오버 — 그림자 + inset 보더 */ .atom-select__listbox { background: var(--atom-bg-layer); border-radius: var(--atom-radius-r3); box-shadow: var(--atom-shadow-s2), inset 0 0 0 1px var(--atom-border-subtle); } /* 모달 — 가장 높은 층위 */ .atom-search-dialog__panel { border: 1px solid var(--atom-border-subtle); background: var(--atom-bg-layer); box-shadow: var(--atom-shadow-s3); } ``` 그림자만으로는 다크 모드에서 리스트박스 경계가 사라지기 때문에 `inset` 보더를 함께 넣었습니다. --- ## 5. 사용 가이드 ### 그림자는 떠 있는 표면에만 씁니다 그림자는 표면이 다른 표면 위에 떠 있음을 나타냅니다. 떠 있지 않은 요소에 그림자를 주면 실제로 없는 층위가 있는 것처럼 보입니다. - ✅ **Do** 팝오버·다이얼로그처럼 실제로 위에 뜨는 것에만 씁니다. - ❌ **Don't** 강조하고 싶다는 이유로 본문 안 카드에 그림자를 넣지 않습니다. ### 층위가 같으면 그림자도 같습니다 한 화면 안의 같은 층위 요소가 서로 다른 그림자를 쓰면 층위 순서를 알 수 없습니다. 층위를 먼저 정하고 그에 맞는 단계를 고릅니다. - ✅ **Do** 모든 팝오버에 `--atom-shadow-s2` 를 씁니다. - ❌ **Don't** 같은 종류의 팝오버끼리 `s2` 와 `s3` 를 섞지 않습니다. ### 상시 노출 요소에는 쓰지 않습니다 그림자는 팝오버, 다이얼로그, 토스트처럼 일시적으로 나타났다 사라지는 요소에 씁니다. 항상 보이는 요소는 떠 있는 층위로 보지 않습니다. - ✅ **Do** 상시 노출되는 영역은 `--atom-border-subtle` 로 경계를 그립니다. - ❌ **Don't** 헤더·사이드바에 그림자를 상시로 걸지 않습니다. ### 그림자와 보더를 함께 쓰는 것을 기본으로 그림자만으로 경계를 그리면 다크 모드에서 표면이 배경과 구분되지 않습니다. - ✅ **Do** `box-shadow: var(--atom-shadow-s2), inset 0 0 0 1px var(--atom-border-subtle)` - ❌ **Don't** 다크 모드를 확인하지 않고 그림자만 걸지 않습니다. ### 그림자로 hover 를 표현하지 않습니다 hover 에서 그림자를 키우면 요소가 커진 것처럼 보여 레이아웃이 흔들립니다. ATOM 은 hover 를 배경색으로 표현합니다 (`--atom-bg-nav-hover` · `--atom-bg-side-hover`). ### 접근성 - **그림자만으로 경계를 알리지 않습니다** — Windows 고대비 모드에서는 `box-shadow` 가 렌더링되지 않습니다. 표면의 경계가 중요하면 보더를 함께 둡니다. - **층위는 DOM 순서와 일치해야 합니다** — 그림자로 위에 있는 것처럼 보이는데 실제 포커스 순서는 아래에 있으면 키보드 사용자에게 순서가 어긋납니다. - **딤(scrim)** — 모달 뒤 `--atom-bg-scrim` 은 배경 조작이 막혔음을 나타내는 표시입니다. 딤을 쓰는 층위는 배경 요소를 `inert` 로 만들어야 합니다.