# Dialog 현재 작업 위에 떠서 사용자의 입력이나 확인을 받는 모달입니다. > 원본: `03_ASSET_GUIDE/catalog/dialog.js` · CSS: `03_ASSET_GUIDE/styles/elements/dialog.css` > kind `component` · category `overlay` · status `stable` · specSource `measured` > 함께 필요한 CSS: `button` (Header 의 닫기와 Footer 의 액션이 실제 [Button](./button.md) 입니다) --- ## 1. 구성 뒤 화면을 덮는 **Scrim** 과 그 위에 뜨는 **Panel** 두 층으로 나뉩니다. Panel 은 위에서부터 **Header** · **Body** · **Footer** 세 칸이고, 내용이 길어질 때 스크롤하는 것은 Body 하나뿐입니다. ``` ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ ① ░░ ┌────────────────────────────────────┐ ░░ ░░ │ 알림 받을 시간 ✕ │ ░░ ② ③ ④ ⑤ ░░ │ 설정한 시간에만 알림을 보냅니다. │ ░░ ⑥ ░░ │ [ 취소 ] [ 저장 ] │ ░░ ⑦ ░░ └────────────────────────────────────┘ ░░ ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ ``` | # | 요소 | 필수 | 클래스 | 설명 | |---|---|---|---|---| | ① | Scrim | 필수 | `atom-dialog-scrim` | 뒤 배경을 덮는 딤. 열려 있는 동안 뒤 내용은 조작할 수 없습니다. | | ② | Panel | 필수 | `atom-dialog` | 떠 있는 층. 최대 폭과 최대 높이를 가집니다. | | ③ | Header | 필수 | `atom-dialog__header` | 제목과 닫기 버튼. 스크롤해도 고정됩니다. | | ④ | Title | 필수 | `atom-dialog__title` | 필수입니다. `aria-labelledby` 가 가리키는 대상입니다. | | ⑤ | Close | 선택 | `atom-dialog__close` | 선택입니다. 아이콘만 있는 [Button](./button.md) 을 씁니다. | | ⑥ | Body | 선택 | `atom-dialog__body` · `atom-dialog__description` | 내용. 길면 이 영역만 스크롤합니다. | | ⑦ | Footer | 선택 | `atom-dialog__footer` | 액션 버튼. 오른쪽 정렬이고 스크롤해도 고정됩니다. | | — | Frame | 문서용 | `atom-dialog-frame` | `--contained` Scrim 이 딤 처리할 대역 뷰포트. 데모 스테이지 전용입니다. | --- ## 2. 종류 / 크기 ### Size `medium` 이 기본이고 최대 폭 480px 입니다. 표나 목록이 들어가 480px 로는 읽기 어려울 때만 `large`(800px) 를 씁니다. 최대 폭이므로 화면이 좁으면 그만큼 줄어듭니다. | Size | 최대 폭 | 쓸 곳 | | --- | --- | --- | | `medium` (기본) | 480px | 확인, 짧은 폼 | | `large` | 800px | 표·목록이 들어가는 내용 | 폭만 달라지고 패딩과 글자 크기는 두 사이즈가 같습니다. ### 높이와 스크롤 최대 높이는 **뷰포트의 80%** 입니다. 위아래로 배경이 보여야 떠 있는 층으로 인식됩니다. 내용이 그보다 길면 **Body 만 스크롤하고 Header 와 Footer 는 고정**합니다. 전체가 스크롤되면 액션 버튼을 보려고 끝까지 내려가야 합니다. Body 에 스크롤이 생기면 Panel 에 `data-body-scroll` 을 붙여 Header 아래와 Footer 위에 구분선을 넣습니다. 선이 없으면 잘린 문장이 레이아웃 오류처럼 보입니다. ### Header · Footer 조합 필수는 Title 하나뿐입니다. Close 와 Body 와 Footer 는 상황에 따라 빠집니다. 세 칸이 각자 여백을 가지므로, Body 나 Footer 가 없어도 24px 가장자리 여백은 유지됩니다. | 조합 | 예 | | --- | --- | | 제목 + 액션 2개 · 본문 없음 | "저장하지 않고 나갈까요" + 계속 작성 / 나가기 | | 제목 + 닫기 + 본문 · 푸터 없음 | "임시 권한이 발급됐어요" | | 제목만 | "알림 설정" | ### 좁은 화면 Scrim 의 좌우 여백 16px 이 그대로 좁은 화면의 여백이 됩니다. 375px 화면에서 Panel 은 화면 폭의 약 91% 입니다. 별도의 브레이크포인트는 두지 않았습니다. 프리뷰 캔버스가 데스크톱 뷰포트 안의 400px 상자라서, `min-width` 쿼리를 걸면 카드 썸네일에도 그 규칙이 적용되기 때문입니다. | 대상 | 값 | | --- | --- | | Panel 폭 | 화면 폭 − 32px (375px 화면에서 약 91%) | | Panel 최대 높이 | `min(80vh, 100%)` | | Footer 배치 | 라벨이 한 줄에 안 들어가면 세로로 쌓습니다 | > [!NOTE] > **세로로 쌓을 때는 아래가 주요 액션입니다** > `__footer--stack` 은 방향만 바꾸고 순서는 그대로 둡니다. 가로에서 오른쪽 끝이던 주요 > 액션이 세로에서는 맨 아래로 갑니다. 엄지가 닿기 쉬운 위치에 주요 액션을 두기 위해서입니다. Bottom Sheet 로의 전환은 하지 않습니다. `bottom-sheet` 프리뷰가 이미 따로 있으므로 폭만 줄이는 쪽을 택했습니다. 컴포넌트가 화면 크기에 따라 둘로 갈리면 어느 문서를 참고해야 할지 불분명해집니다. ### 치수 | 대상 | 속성 | 값 | | --- | --- | --- | | Scrim | padding | 16px — 좁은 화면의 좌우 여백을 겸합니다 | | Panel | max-width | 480px (`medium`) / 800px (`large`) | | Panel | max-height | `min(80vh, 100%)` | | Header | padding | 24px 24px 12px | | Title | font | 18px / 700 / line-height 1.4 | | Body | padding-inline | 24px | | Body | font | 15px / line-height 1.7 | | Footer | padding | 24px | | Footer | gap | 8px | | Close | margin | −6px −8px −6px 0 — 아이콘 버튼의 여백을 패널 가장자리로 되돌립니다 | > [!NOTE] > **Panel 에는 패딩이 없습니다** > 24px 가장자리 여백은 Header · Body · Footer 가 각자 갖습니다. Panel 에 패딩을 주면 Body 가 > 스크롤할 때 내용이 테두리에서 24px 안쪽에서 잘려 렌더 버그처럼 보입니다. Header 의 닫기 버튼과 Footer 의 액션은 [Button](./button.md) 을 그대로 쓰며, Dialog 전용 버튼 스타일은 없습니다. 닫기는 `layout_icon-only` + `variant_ghost`, Footer 는 기본 `medium`(47px) 입니다. ### 클래스 계약 `styles/elements/dialog.css` 헤더와 원본 `registry.json` `css.blocks` 기준입니다. 클래스가 없는 값(—)은 기본 스타일 그대로입니다. | 대상 | Prop | 값 | 클래스 | |---|---|---|---| | `atom-dialog` | `size` | `medium` · `large` | — · `--size_large` | | `atom-dialog__footer` | `stackActions` | `false` · `true` | — · `atom-dialog__footer--stack` | | `atom-dialog-scrim` | (구현 `contained`) | `false` · `true` | — · `--contained` | 상태 선택자: `atom-dialog-scrim[data-state="open\|closed"]` · `atom-dialog[data-state="closed"]` · `atom-dialog[data-body-scroll]` · `atom-dialog:focus` --- ## 3. 상태 열림/닫힘은 Scrim 의 `data-state` 로 표현합니다. `closed` 는 `display: none` 이므로 닫힌 Dialog 는 레이아웃을 차지하지 않습니다. | State | 표현 | 선택자 | | --- | --- | --- | | `open` | Scrim · Panel 진입 애니메이션 | `.atom-dialog-scrim[data-state="open"]` | | `closed` | `display: none` | `.atom-dialog-scrim[data-state="closed"]` · `.atom-dialog[data-state="closed"]` | | Body 스크롤 | Header 아래 · Footer 위 1px 구분선 | `.atom-dialog[data-body-scroll]` | | Panel focus | 링을 그리지 않음 | `.atom-dialog:focus` | | 대상 | 속성 | 값 | | --- | --- | --- | | Scrim | opacity | 0 → 1 | | Panel | opacity · transform | 0 → 1 · `translateY(8px) scale(0.97)` → 없음 | | 둘 다 | duration · easing | `--atom-duration-base`(200ms) · `--atom-ease-out` | 퇴장 곡선은 Motion 에 `ease-in` 이 없어 정의하지 않았습니다. 닫기는 JS 가 `data-state="closed"` 로 즉시 감춥니다. > [!WARNING] > **모션 감소에서는 키프레임을 끕니다** > `base.css` 의 전역 규칙은 모든 애니메이션을 0.01ms 로 줄이지만 **첫 프레임은 그대로 > 그립니다.** 그러면 Panel 이 8px 아래에 투명하게 한 번 그려지므로, > `prefers-reduced-motion: reduce` 에서는 `animation: none` 으로 키프레임 자체를 없앴습니다. --- ## 4. Props | Prop | Type | Default | 필수 | 설명 | |---|---|---|---|---| | `open` | `boolean` | `false` | | 열림 상태입니다. Scrim 의 `data-state` 로 내려갑니다. | | `title` | `ReactNode` | | ● | 제목입니다. | | `description` | `ReactNode` | | | 제목 아래 설명입니다. | | `size` | `"medium" \| "large"` | `"medium"` | | 최대 폭 480 · 800px 입니다. | | `role` | `"dialog" \| "alertdialog"` | `"dialog"` | | 확인이 강제되는 경고면 `alertdialog`. | | `showClose` | `boolean` | `true` | | 헤더 닫기 버튼입니다. `alertdialog` 는 `false`. | | `dismissOnScrim` | `boolean` | `true` | | Scrim 클릭으로 닫습니다. 파괴적 작업과 `alertdialog` 는 `false`. | | `dismissOnEsc` | `boolean` | `true` | | `Esc` 로 닫습니다. | | `stackActions` | `boolean` | `false` | | Footer 를 세로로 쌓습니다. 주요 액션이 맨 아래로 갑니다. | | `onOpenChange` | `(open: boolean) => void` | | | 열림 상태가 바뀔 때 호출됩니다. | ### Parts (compound) | Part | 요소 | 설명 | |---|---|---| | `Dialog` | `
` > `