Groo Design

Dialog

1280 이상에서 화면 정중앙에 뜨는 대화상자. 입력 폼 · 상세처럼 지금 화면을 떠나지 않고 할 일을 띄운다 — 같은 내용을 1280 미만에서는 Bottom Sheet 로 띄운다(한 부품이 폭으로 바꾼다 — Responsive Dialog). 되돌릴 수 없는 확인은 Alert Dialog, 트리거에 붙는 짧은 내용은 Popover, 화면 높이를 넘는 긴 내용은 페이지다.

구조는 당근 SEED Dialog · Responsive Dialog(Apache-2.0)를 따른다 — 딤 · 대화상자 · 머리(제목 · 설명 · 닫기) · 본문 · 바닥, 본문만 스크롤. 값은 groo 토큰이고, SEED 와 다른 자리는 맨 아래 "SEED 와 다른 점" 에 적었다(2026-10-02 사용자 결정).

수치 원본은 dialog.yaml이다. 수치 표 자리([표: …])와 그림 자리([그림: …])는 사이트가 그 파일로 그린다 — GitHub 에서는 그 파일 · 그림 코드로 가는 링크로 보인다.

hr.groo.app
휴가
연연차10월 12일 (월)~10월 13일 (화)2일
반반차(오후)9월 25일 (금)0.5일
경경조 휴가9월 4일 (금)1일
연연차8월 14일 (금)1일
휴가 신청
승인되면 알려드려요.
연차
10월 12일~10월 13일
desk.groo.app
가계부
점점심 식사식비, 현대카드 M−12,000원
지지하철교통, 국민 체크카드−1,450원
월월급수입, 국민 주계좌+3,200,000원
편편의점식비, 현금−4,300원
거래 상세
  • 금액−12,000원
  • 내용점심 식사
  • 카테고리식비
  • 결제 수단현대카드 M
  • 날짜10월 1일 (목) 오후 12:30
hr.groo.app
휴가
연연차10월 12일 (월)~10월 13일 (화)2일
반반차(오후)9월 25일 (금)0.5일
경경조 휴가9월 4일 (금)1일
연연차8월 14일 (금)1일
휴가 신청
승인되면 알려드려요.
연차
10월 12일~10월 13일
desk.groo.app
가계부
점점심 식사식비, 현대카드 M−12,000원
지지하철교통, 국민 체크카드−1,450원
월월급수입, 국민 주계좌+3,200,000원
편편의점식비, 현금−4,300원
거래 상세
  • 금액−12,000원
  • 내용점심 식사
  • 카테고리식비
  • 결제 수단현대카드 M
  • 날짜10월 1일 (목) 오후 12:30
휴가 신청 · 거래 상세 — 라이트 · 다크

직접 골라 보기

크기 · 쓰임(입력 폼 · 조회) · 설명 · 본문 길이 · 창 폭(1280 위 · 아래)을 고르면 스펙대로 그린 대화상자(1280 미만이면 시트)와 그 코드가 바뀐다. 실제로 열고 닫고 본문을 스크롤할 수 있다.

휴가
점심 식사식비 · 현대카드 M−12,000원
지하철교통 · 국민 체크카드−1,450원
월급수입 · 국민 주계좌+3,200,000원
영화문화 · 국민 체크카드−15,000원
크기 size
쓰임
설명 description
본문 길이
끝 흐림 scrollFog
창 폭 — 1280 에서 바뀐다
모드
브랜드

창 폭은 그림 속 화면을 바꾼다(열린 채 바꾸면 쓰던 값은 그대로 표면만 바뀐다). large 는 이 그림 속 창보다 넓어 화면 폭 − 40 로 줄어든다.

import { Button } from "@/components/ui/button"
import { ResponsiveDialog, ResponsiveDialogBody, ResponsiveDialogCancel, ResponsiveDialogContent, ResponsiveDialogFooter } from "@/components/ui/dialog"

{/* form — 바깥 누르기 · 끌어내리기로 닫지 않는다. dirty — 닫기 전에 "작성한 내용이 사라져요" 를 묻는다 */}
<ResponsiveDialog open={open} onOpenChange={setOpen} form dirty={isDirty}>
  <ResponsiveDialogContent title="휴가 신청" description="승인되면 알려드려요.">
    <ResponsiveDialogBody>
      <Field label="사유">…</Field>
      <Field label="연락처">…</Field>
    </ResponsiveDialogBody>
    <ResponsiveDialogFooter>
      {/* 1280 이상에서만 그린다 — 시트에서는 위 닫기 버튼이 맡는다 */}
      <ResponsiveDialogCancel>취소</ResponsiveDialogCancel>
      <Button onClick={submit}>신청</Button>
    </ResponsiveDialogFooter>
  </ResponsiveDialogContent>
</ResponsiveDialog>

Anatomy

desk.groo.app
가계부
점점심 식사식비, 현대카드 M−12,000원
지지하철교통, 국민 체크카드−1,450원
월월급수입, 국민 주계좌+3,200,000원
편편의점식비, 현금−4,300원
거래 상세
  • 금액−12,000원
  • 내용점심 식사
  • 카테고리식비
  • 결제 수단현대카드 M
  • 날짜10월 1일 (목) 오후 12:30
  • 할부일시불
  • 청구 회차11월 14일 결제
  • 메모회의 뒤 점심
ⓐ Overlayⓑ Containerⓒ Header — 닫기는 조회 · 안내만ⓓ Body — 넘치면 이 안만 스크롤 · 넘칠 수 있으면 끝 흐림ⓔ Footer
대화상자는 딤 · 대화상자 · 머리 · 본문 · 바닥으로, 넘칠 수 있는 본문은 끝이 늘 흐리고 스크롤하면 머리 아래 선이 생긴다
부위설명
ⓐ Overlay딤 — 대화상자 뒤 화면 전체. 조회 대화상자는 누르면 닫힌다(입력 폼은 무시).
ⓑ Container대화상자 — 화면 정중앙, medium 480 · large 800, 높이는 화면의 80% 까지.
ⓒ Header머리 — 제목 · 설명. 조회 · 안내 대화상자만 오른쪽에 닫기 버튼. 두 단계 흐름의 2단계는 왼쪽에 ←(아래 "두 단계 흐름의 머리 ←"), 동작이 여럿인 상세는 닫기 왼쪽에 ⋮ 하나(아래 "머리 오른쪽 동작").
ⓓ Body본문 — 넘치면 이 안에서만 스크롤한다(머리 · 바닥은 그대로). 넘칠 수 있는 본문은 끝을 흐린다(Scroll Fog).
ⓔ Footer바닥 — 버튼, 오른쪽 정렬.

부위

부위설명
overlay딤 — 대화상자 뒤 화면 전체
root대화상자 — 화면 정중앙
closeButton닫기 — 조회 · 안내 대화상자만. 입력 폼에는 두지 않는다(바닥 취소). 이름 "닫기"
headerAction머리 오른쪽 동작 — 닫기 왼쪽(닫기가 없으면 오른쪽 끝)의 ⋮ 하나(축 headerAction). 이름 "{항목 이름} 더보기"
header머리 — 제목 · 설명(· 닫기)
title제목
description설명 — 덧붙일 말이 있을 때만
divider머리 아래 선 — 본문이 위로 스크롤됐을 때만
body본문 — 넘치면 이 안에서만 스크롤(머리 · 바닥은 고정)
fog끝 흐림 — Scroll Fog(scroll-fog.yaml 의 overlayBody). 넘칠 수 있는 본문에만 걸고, 걸면 늘 켜져 있다
footer바닥 — 버튼, 오른쪽 정렬
focusRing키보드 포커스 링(웹)

Properties

Size

medium 480 (기본) — 일반 입력 폼 · 상세, large 800 — 복잡한 설정 · 많은 조회. 높이는 내용만큼이고 화면 높이의 80% 를 넘지 않는다. 넘치는 만큼 본문이 스크롤된다.

hr.groo.app
휴가
연연차10월 12일 (월)~10월 13일 (화)2일
반반차(오후)9월 25일 (금)0.5일
경경조 휴가9월 4일 (금)1일
연연차8월 14일 (금)1일
휴가 신청
승인되면 알려드려요.
연차
10월 12일~10월 13일
medium 480(기본)일반 입력 폼 · 상세
hr.groo.app
급여
연연차10월 12일 (월)~10월 13일 (화)2일
반반차(오후)9월 25일 (금)0.5일
경경조 휴가9월 4일 (금)1일
연연차8월 14일 (금)1일
10월 급여 명세
10월 25일에 들어와요.
  • 기본급3,200,000원
  • 식대200,000원
  • 야근 수당184,000원
  • 국민연금−144,000원
  • 건강보험−113,440원
  • 근무일22일
  • 연장 근무8시간
  • 쓴 연차1일
  • 소득세−98,320원
  • 실지급액3,228,240원
large 800복잡한 설정 · 많은 조회 — 넓은 표 · 두 단

높이는 내용만큼, 화면 높이의 80% 까지 — 넘치는 만큼 본문이 스크롤된다. 화면이 좁으면 좌우 20 을 남기고 준다. 모서리 20 · 그림자 없음(딤과 표면 색으로 뜬다)

크기 — medium 480 · large 800

크기

size너비설명
medium (기본)480px480 — 기본. 일반 입력 폼 · 상세
large800px800 — 복잡한 설정 · 많은 조회

머리 · 본문 · 바닥

머리는 위 24 · 좌우 24 · 아래 16, 제목 22 / 30 · 700 · 설명 16 / 22 · 사이 6. 본문은 좌우 24. 바닥은 위 16 · 좌우 24 · 아래 24 에 버튼을 오른쪽으로 모은다 — Button small 36, [취소] [저장] 순서. 위로 스크롤하면 머리 아래 1px 선이 생긴다.

공통

부위속성값비고
overlay배경overlay-dim-light rgba(0, 0, 0, 0.50) · 다크 overlay-dim-dark rgba(0, 0, 0, 0.65)groo 딤(v102)
overlayz-index100z-modal — specs/z-index.md L2
root최대 너비화면 폭 − 40좌우 20 은 남긴다
root최대 높이화면 높이의 80%dvh 우선 — 넘치면 본문만 스크롤
root모서리radius-r5 20px
root배경color-bg-layer-floating #FFFFFF · 다크 #2D3346
root그림자none그림자 없이 딤과 표면 색으로 뜬다(SEED · Elevation)
rootz-index101z-modal-content — specs/z-index.md L2
header위 여백spacing-x6 24px
header좌우 여백spacing-x6 24px
header아래 여백spacing-x4 16px
header간격spacing-x1_5 6px제목 ↔ 설명. 닫기 버튼이 있으면 오른쪽 52(24 + 아이콘 22 + 6)
title글자text-t8 22px / 30px
title굵기700
title글자·아이콘color-fg-neutral #1A1F2E · 다크 #F5F6FA
description글자text-t5 16px / 22px
description굵기400
description글자·아이콘color-fg-neutral-muted #535866 · 다크 #B7BDCC
closeButton크기52px투명 상자 — 보이는 건 아이콘 22
closeButton모서리radius-r3 12px
closeButton아이콘 크기22pxlucide x
closeButton색color-fg-neutral-subtle #62697A · 다크 #A2A8B7
closeButton위 거리28px아이콘이 대화상자 위에서 28 · 오른쪽 24 — 제목 첫 줄 가운데와 맞는다
closeButton오른쪽 거리spacing-x6 24px
closeButton터치 영역52 × 52
closeButton전환 대상background-color, scale
closeButton전환 시간motion-duration-color-transition 150ms
closeButton전환 곡선motion-ease-easing cubic-bezier(0.35, 0, 0.35, 1)
body좌우 여백spacing-x6 24px
body위 여백spacing-x6 24px머리가 없을 때만 — 머리가 있으면 0
body아래 여백spacing-x6 24px바닥이 없을 때만 — 바닥이 있으면 0
body세로 넘침auto
divider높이1px
divider배경color-stroke-neutral-subtle #EDEFF3 · 다크 #353B4D안쪽 그림자로 그린다 — 본문이 첫 자식이면(머리가 없으면) 그리지 않는다
divider전환 시간motion-duration-color-transition 150ms
divider전환 곡선motion-ease-easing cubic-bezier(0.35, 0, 0.35, 1)
footer위 여백spacing-x4 16px
footer좌우 여백spacing-x6 24px
footer아래 여백spacing-x6 24px
footer간격spacing-x2 8px
footer가로 맞춤flex-end오른쪽 정렬 — [취소] [저장]
footer버튼 크기small 36Button small(Button 결정)

본문 끝 흐림

목록 · 긴 폼처럼 넘칠 수 있는 본문은 Scroll Fog 를 건다(scrollFog) — 위 20 · 아래 80 이 늘 흐리고, 본문 안에 그만큼 여백을 둬 끝까지 내리면 흐림이 빈 여백 위에 놓인다. 넘쳤는지 재서 켜고 끄지 않는다 — 걸 본문인지는 내용의 종류로 정하고, 칸 두셋처럼 늘 들어맞는 본문에는 걸지 않는다. 1280 미만의 시트에서도 같다(Bottom Sheet).

거래 상세
  • 금액−12,000원
  • 내용점심 식사
  • 카테고리식비
  • 결제 수단현대카드 M
  • 날짜10월 1일 (목) 오후 12:30
  • 할부일시불
  • 청구 회차11월 14일 결제
  • 메모회의 뒤 점심
맨 위위 20 흐림은 빈 여백(20) 위 — 아래 80 이 흐려 더 있음을 알린다
거래 상세
  • 금액−12,000원
  • 내용점심 식사
  • 카테고리식비
  • 결제 수단현대카드 M
  • 날짜10월 1일 (목) 오후 12:30
  • 할부일시불
  • 청구 회차11월 14일 결제
  • 메모회의 뒤 점심
위로 스크롤됨흐림은 그대로 · 머리 아래 1px 선이 본문과 머리를 가른다
거래 상세
  • 금액−12,000원
  • 내용점심 식사
  • 카테고리식비
  • 결제 수단현대카드 M
  • 날짜10월 1일 (목) 오후 12:30
  • 할부일시불
  • 청구 회차11월 14일 결제
  • 메모회의 뒤 점심
끝까지아래 80 흐림은 빈 여백 위 — 마지막 줄은 흐림 밖에 다 보인다
본문 스크롤 — 늘 켜진 끝 흐림(위 20 · 아래 80) · 머리 아래 선

본문 끝 흐림

scrollFogbody 위 여백body 아래 여백body 스크롤 여유fog 크기fog 마스크설명
off (기본)—————기본 — 늘 들어맞는 본문(칸 두셋 · 짧은 안내)
on20px — 머리가 있으면 20(머리 아래 16 은 그대로 두고 본문 안에 더한다), 머리가 없으면 24 그대로 — 흐림 깊이 이상80px — 바닥이 있어도 — 끝까지 내려도 마지막 줄이 흐림 아래 남지 않는다위 20px · 아래 80px — 키보드로 옮긴 칸이 흐림 아래 멈추지 않게위 20px · 아래 80px — 본문 상자의 위 · 아래 끝gradient-fade-mask — Scroll Fog overlayBody(scroll-fog.yaml) — 늘 켜져 있다. 넘쳤는지 재서 켜고 끄지 않는다넘칠 수 있는 본문(목록 · 긴 폼 · 데이터에 따라 길어지는 내용) — 위 20 · 아래 80 이 늘 흐리다

State

상태모습
enabled열림 — 딤 위 대화상자
scrolled본문이 위로 스크롤됨 — 머리 아래 1px stroke-neutral-subtle
pressed닫기 버튼 — bg-layer-floating-pressed + 2px 거리 축소
focused키보드 포커스에만 링 2px · 띄움 2px

상태

상태closeButton 배경closeButton 배율divider 배경divider 불투명도링 두께링 간격링 색
enabled 열림——color-stroke-neutral-subtle #EDEFF3 · 다크 #353B4D — 안쪽 그림자로 그린다 — 본문이 첫 자식이면(머리가 없으면) 그리지 않는다————
scrolled 본문이 위로 스크롤됨 — 머리 아래 선——color-stroke-neutral-subtle #EDEFF3 · 다크 #353B4D — 안쪽 그림자로 그린다 — 본문이 첫 자식이면(머리가 없으면) 그리지 않는다1 — 본문을 위로 스크롤하면 나타난다———
pressed 닫기 버튼 누름color-bg-layer-floating-pressed #F7F8FD · 다크 #353B4D — SEED bg.transparent-pressed 의 떠 있는 표면 짝(v102)2px 거리 축소 — 기준 52 → 0.962 — 모션 줄이기면 하지 않는다color-stroke-neutral-subtle #EDEFF3 · 다크 #353B4D — 안쪽 그림자로 그린다 — 본문이 첫 자식이면(머리가 없으면) 그리지 않는다————
focused 웹 — 키보드 포커스에만 링——color-stroke-neutral-subtle #EDEFF3 · 다크 #353B4D — 안쪽 그림자로 그린다 — 본문이 첫 자식이면(머리가 없으면) 그리지 않는다—2px2pxcolor-stroke-focus-ring #1A1F2E · 다크 #F5F6FA

모션

전환시간곡선대상비고
열림 — 대화상자motion-duration-d4 200msmotion-ease-enter-expressive cubic-bezier(0.03, 0.4, 0.1, 1)scale 1.3 → 1, opacity 0 → 1크게 나타나며 줄어든다(SEED) — 모션 줄이기면 150ms 서서히 나타남
열림 — 딤motion-duration-d2 100msmotion-ease-enter cubic-bezier(0, 0, 0.15, 1)opacity 0 → 1
닫힘 — 대화상자motion-duration-d2 100msmotion-ease-exit cubic-bezier(0.35, 0, 1, 1)opacity → 0축소 없이 사라진다
닫힘 — 딤motion-duration-d2 100msmotion-ease-exit cubic-bezier(0.35, 0, 1, 1)opacity → 0
머리 아래 선motion-duration-color-transition 150msmotion-ease-easing cubic-bezier(0.35, 0, 0.35, 1)box-shadow
누름 — 닫기 버튼motion-duration-pressed-scale 150msmotion-ease-pressed-scale cubic-bezier(0, 0, 0.15, 1)scale

Guidelines

한 부품이 폭으로 바뀐다

폼 · 상세는 한 부품(ResponsiveDialog)으로 짠다 — 1280 이상에서 Dialog, 미만에서 Bottom Sheet 다. 머리 · 본문 · 바닥은 같고, 닫는 자리만 표면에 맞춰 바뀐다(아래 "닫기 버튼과 취소"). 경계는 Input Button 과 같은 1280 이라, 대화상자 안의 날짜 칸은 늘 팝오버로, 시트 안의 날짜 칸은 늘 시트로 열린다 — 표면이 엇갈려 겹치지 않는다.

desk.groo.app
가계부
점점심 식사식비, 현대카드 M−12,000원
지지하철교통, 국민 체크카드−1,450원
월월급수입, 국민 주계좌+3,200,000원
편편의점식비, 현금−4,300원
거래 추가
12,000원
10월 1일 (목)
식비 · 점심
1280 이상 — 대화상자바닥 취소 · 저장(머리 닫기 없음) · 날짜 칸은 팝오버로
9:41
가계부
10월 1일 (목)−18,750원
점점심 식사식비, 현대카드 M−12,000원
지지하철교통, 국민 체크카드−1,450원
월월급수입, 국민 주계좌+3,200,000원
편편의점식비, 현금−4,300원
영영화문화, 국민 체크카드−15,000원
스스타벅스카페, 현대카드 M−5,600원
거래 추가
12,000원
10월 1일 (목)
식비 · 점심
1280 미만 — 시트위 닫기 + 바닥 저장(바닥 취소 없음) · 날짜 칸은 시트로
같은 폼 — 1280 이상 대화상자 · 미만 시트

입력 폼은 실수로 닫히지 않게

입력 폼 대화상자는 바깥(딤)을 눌러도 닫히지 않는다. 바닥 [취소] · Esc 로 닫고(1280 미만의 시트는 뒤로 가기로도), 바뀐 값이 있으면 닫기 전에 "작성한 내용이 사라져요" 를 묻는다(Field 의 규칙). 조회 · 안내 대화상자는 바깥 누르기 · Esc 로 닫힌다.

hr.groo.app
휴가
연연차10월 12일 (월)~10월 13일 (화)2일
반반차(오후)9월 25일 (금)0.5일
경경조 휴가9월 4일 (금)1일
연연차8월 14일 (금)1일
휴가 신청
승인되면 알려드려요.
연차
10월 12일~10월 13일
입력 폼 — 휴가 신청바닥 취소 · Esc 로 닫는다(뒤로 가기는 1280 미만의 시트만). 바뀐 값이 있으면 먼저 묻는다
desk.groo.app
가계부
점점심 식사식비, 현대카드 M−12,000원
지지하철교통, 국민 체크카드−1,450원
월월급수입, 국민 주계좌+3,200,000원
편편의점식비, 현금−4,300원
거래 상세
  • 금액−12,000원
  • 내용점심 식사
  • 카테고리식비
  • 결제 수단현대카드 M
  • 날짜10월 1일 (목) 오후 12:30
조회 — 거래 상세바깥 누르기 · Esc · 머리 닫기로 닫힌다
닫기 — 입력 폼은 바깥을 눌러도 닫히지 않는다

닫기 버튼과 취소 — 하나만

대화상자닫는 자리
입력 폼(저장 · 신청처럼 주 작업이 있다)바닥 [취소] [저장] — 머리 닫기 버튼 없음
조회 · 안내머리 닫기 버튼 — 바닥 버튼은 다른 동작(수정 · 삭제)이 있을 때만

닫기 버튼과 바닥 취소를 함께 두지 않는다. 1280 미만의 시트에서는 입력 폼도 위 닫기 버튼 + 바닥 [저장] 이다(Bottom Sheet).

hr.groo.app
휴가
연연차10월 12일 (월)~10월 13일 (화)2일
반반차(오후)9월 25일 (금)0.5일
경경조 휴가9월 4일 (금)1일
연연차8월 14일 (금)1일
휴가 신청
승인되면 알려드려요.
연차
10월 12일~10월 13일
입력 폼
desk.groo.app
가계부
점점심 식사식비, 현대카드 M−12,000원
지지하철교통, 국민 체크카드−1,450원
월월급수입, 국민 주계좌+3,200,000원
편편의점식비, 현금−4,300원
거래 상세
  • 금액−12,000원
  • 내용점심 식사
  • 카테고리식비
  • 결제 수단현대카드 M
  • 날짜10월 1일 (목) 오후 12:30
조회
이렇게 — 입력 폼은 바닥 [취소] [신청], 조회 · 안내는 머리 닫기 — 닫는 자리가 하나다
hr.groo.app
휴가
연연차10월 12일 (월)~10월 13일 (화)2일
반반차(오후)9월 25일 (금)0.5일
경경조 휴가9월 4일 (금)1일
연연차8월 14일 (금)1일
휴가 신청
승인되면 알려드려요.
연차
10월 12일~10월 13일
이렇게 하지 않는다 — 머리 닫기와 바닥 취소를 함께 — 닫는 길이 둘이라 어느 쪽인지 망설인다
닫는 자리 — 폼은 바닥 취소 · 조회는 머리 닫기 · 둘을 함께

두 단계 흐름의 머리 ←

한 부품 안에서 1단계(종류 고르기) → 2단계(폼)로 가는 흐름은 1280 이상의 대화상자에서도 2단계 머리 왼쪽에 ← 를 단다 — 시트와 같은 부품(onBack) · 같은 값(보이는 원 40 · 아이콘 24 · 누르는 영역 44 · 아이콘이 왼쪽 24 · 제목은 원 오른쪽 4 에서 · 제목 첫 줄 가운데)이다. 수치 원본은 bottom-sheet.yaml 의 축 back 하나다. ← 는 1단계로 한 단계 되돌리고, 쓴 값이 있으면 "작성한 내용이 사라져요" 를 묻고 돌아가며 그 값을 버린다. 입력 폼 대화상자라 머리 닫기 버튼은 없고 바닥 [취소] 가 닫는다 — ← 는 닫지 않는다. 규칙 전문은 Bottom Sheet 의 "두 단계 시트의 머리 ←"(2026-10-29 사용자 결정 17-1 Q3).

머리 오른쪽 동작 — ⋮ 하나

들어간 상세에서 그 항목의 동작이 여럿이면 머리 오른쪽에 ⋮ 하나를 둔다(headerAction) — 시트와 같은 부품 · 같은 규칙이다(Bottom Sheet 의 "머리 오른쪽 동작"). 누르면 Menu 가 열린다. 자리는 닫기 버튼 왼쪽 사이 4 — 닫기가 없는 입력 폼은 오른쪽 끝이다. 단추는 Button ghost 아이콘만(보이는 36 · 누르는 44), 이름 "{항목 이름} 더보기", 세로는 제목 첫 줄 가운데(닫기 아이콘과 같다). 제목 · 설명은 단추 자리를 비운 남은 폭에서 줄을 바꾼다. 초점 차례는 제목 → 동작 → 닫기, 머리 동작은 하나만 둔다(2026-10-25 I-20 — 웹 DialogContent.headerAction).

머리 오른쪽 동작

headerActionheaderAction 크기headerAction 간격headerAction 터치 영역headerAction 이름headerAction 버튼headerAction centerYheaderAction focusOrderheader 오른쪽 여백설명
hidden (기본)————————기본
shown36pxspacing-x1 4px — 닫기 아이콘 왼쪽 끝 ↔ 동작 단추 4. 닫기가 없는 입력 폼은 오른쪽 끝 — 단추 상자가 대화상자 오른쪽에서 1644 × 44{항목 이름} 더보기 — 아이콘만 있는 단추 — 항목 이름을 앞에 둔다Button ghost iconOnly small — 보이는 36 · 아이콘 16 — 누르는 영역 44 × 44제목 첫 줄 가운데 — 대화상자 위 24 + 줄 높이 30 의 절반 = 39 — 닫기 아이콘 가운데와 같다제목 → 동작 → 닫기 — DOM 차례 그대로 — ← 가 있으면 ← → 제목 → 동작 → 닫기100px — 닫기 자리 52 + 사이 4 + 단추 36 + 8 — 제목 · 설명은 남은 폭에서 줄을 바꾼다. 닫기가 없는 입력 폼은 60(오른쪽 16 + 단추 36 + 8)들어간 상세에서 동작이 여럿일 때 — 머리 ⋮ 하나가 Menu 를 연다. 머리 동작은 하나만 둔다

Alert Dialog 와 견주기

SEED Dialog 문서의 비교 표 네 줄(쓰는 일 · 사용자 응답 · 닫기 버튼 · 크기)에 groo 에서 갈리는 세 줄을 더했다(2026-10-22 사용자 결정 1C — https://claude.ai/artifact/YRPbWzhgD4EDdKsVioU8Zj). 어느 떠 있는 표면을 쓸지 일곱을 한눈에 견준 표는 DESIGN.md Components 의 "시트 · 대화상자 · 확인창 · 팝오버" 한 벌이다(3C).

DialogAlert Dialog
쓰는 일작업 · 조회 · 입력 폼 — 사용자가 내용과 오가며 일한다되돌릴 수 없는 일의 확인 · 꼭 알아야 할 알림
사용자 응답고르지 않고 닫고 돌아가도 된다고르기 전에는 못 지나간다
닫기 버튼조회는 머리 닫기 · 입력 폼은 바닥 [취소](둘을 함께 두지 않는다)없다 — [취소] 가 닫는다
크기medium · large, 높이는 화면의 80% 까지(본문만 스크롤)하나(최대 272), 넘치면 확인창 전체가 스크롤
바깥(딤) · Esc (groo)조회는 둘 다 닫힘 · 입력 폼은 바깥 무시, Esc 는 취소바깥 무시 · Esc 와 뒤로 가기는 취소
1280 미만 (groo)같은 내용이 Bottom Sheet 로 바뀐다(Responsive Dialog)폰에서도 가운데 그대로
버튼 (groo)바닥 오른쪽 small — 시트에서는 large반씩 나란히(길면 세로 · 확정 위), 1280 미만 medium · 이상 small

대화상자에 두지 않는 것

  • 되돌릴 수 없는 확인 → Alert Dialog.
  • 트리거에 붙는 짧은 내용(안내 · 고르기 패널) → Popover.
  • 화면 높이를 넘는 긴 내용 · 단계가 많은 일 → 페이지.
  • 대화상자 위에 대화상자 — 겹쳐도 되는 건 그 안에서 연 Alert Dialog · Popover 뿐이다.

글

제목은 하는 일 — "휴가 신청" · "거래 상세". 설명은 덧붙일 말이 있을 때만 한 문장(해요체 · 마침표). 바닥 버튼은 동작 이름("신청" · "저장") — "확인" 으로 뭉뚱그리지 않는다.

코드

레시피 recipes/shadcn/components/ui/dialog.tsx 를 쓴다(Radix Dialog 위). 폼 · 상세는 ResponsiveDialog — 1280 에서 Dialog 와 Bottom Sheet 를 바꾸고, form 이면 닫는 자리도 표면에 맞춰 바꾼다(대화상자는 바닥 취소, 시트는 위 닫기). form 이 아니면(조회 · 안내) 두 표면 모두 위 닫기 버튼이다. 본문에 List 를 바로 두면 줄이 제 좌우 여백(24)을 가지므로 본문 좌우 여백을 뺀다(className="px-0"). 넘칠 수 있는 본문은 scrollFog 를 준다(위 20 · 아래 80 끝 흐림 — Scroll Fog). 아래 미리보기는 스펙 값으로 그린 모습이다.

입력 폼

휴가

남은 연차 11일

import { Button } from "@/components/ui/button"
import { ResponsiveDialog, ResponsiveDialogBody, ResponsiveDialogCancel, ResponsiveDialogContent, ResponsiveDialogFooter } from "@/components/ui/dialog"

{/* form — 바깥 누르기 · 끌어내리기로 닫지 않는다. dirty — 닫기 전에 "작성한 내용이 사라져요" 를 묻는다 */}
<ResponsiveDialog open={open} onOpenChange={setOpen} form dirty={isDirty}>
  <ResponsiveDialogContent title="휴가 신청" description="승인되면 알려드려요.">
    <ResponsiveDialogBody>
      <Field label="휴가 종류">…</Field>
      <Field label="기간">…</Field>
    </ResponsiveDialogBody>
    <ResponsiveDialogFooter>
      {/* 1280 이상에서만 그린다 — 시트에서는 위 닫기 버튼이 맡는다 */}
      <ResponsiveDialogCancel>취소</ResponsiveDialogCancel>
      <Button onClick={submit}>신청</Button>
    </ResponsiveDialogFooter>
  </ResponsiveDialogContent>
</ResponsiveDialog>

조회

가계부
  • 지하철교통 · 교통 체크카드-1,450원
{/* 조회 — 머리 닫기 버튼(시트에서는 오른쪽 위 원), 바깥 누르기로도 닫힌다. 길이가 데이터에 따라 느는 본문이라 scrollFog */}
<ResponsiveDialog open={open} onOpenChange={setOpen}>
  <ResponsiveDialogContent title="거래 상세">
    <ResponsiveDialogBody scrollFog className="px-0">
      <List>…</List>
    </ResponsiveDialogBody>
  </ResponsiveDialogContent>
</ResponsiveDialog>

Behavior

인터랙션동작
열기200ms 로 크게 나타나며 줄어든다(1.3 → 1). 처음 초점은 대화상자에 간다.
바닥 취소 · 닫기 버튼 · Esc닫는다. 입력 폼에 바뀐 값이 있으면 먼저 묻는다. 뒤로 가기로 닫는 것은 1280 미만(시트)뿐이다 — 데스크톱 브라우저의 뒤로 가기는 페이지를 떠난다.
바깥(딤) 누르기조회 · 안내는 닫는다. 입력 폼은 무시한다.
머리 ⋮ 누르기그 항목의 동작 목록 Menu 를 연다(대화상자 위 L3).
머리 ← 누르기두 단계 흐름의 2단계 — 같은 대화상자 안에서 1단계로 한 단계 되돌린다(닫지 않는다). 쓴 값이 있으면 먼저 "작성한 내용이 사라져요" 를 묻고, 돌아가면 그 값을 버린다.
본문 스크롤본문만 스크롤 — 위로 스크롤되면 머리 아래 선. 끝 흐림(scrollFog)은 걸었으면 늘 그대로다.
닫힌 뒤100ms 로 사라지고, 초점은 연 자리(트리거)로 돌아간다.
열린 동안뒤 화면을 보조 기술에서 숨기고 스크롤을 잠근다. 초점은 대화상자 안을 돈다.
창 폭이 1280 을 넘나듦열린 채 표면이 바뀐다(대화상자 ↔ 시트). 값은 폼(부모)이 들고 있어 그대로다 — 입력칸의 값을 대화상자 · 시트 안에만 두지 않는다.

Accessibility

기준검증
WCAG 1.4.3 Color contrast(글자 ≥ 4.5:1)제목 fg-neutral 떠 있는 표면 위 16.41 · 다크 11.62, 설명 fg-neutral-muted 7.11 · 6.67 ✓
WCAG 1.4.11 Non-text contrast(≥ 3:1)닫기 아이콘 fg-neutral-subtle 5.50 · 5.27 ✓. 키보드 포커스 링 Desk 8.38 · 5.28 · HR 8.17 · 5.26 ✓
WCAG 2.4.3 Focus order열면 대화상자로, 닫으면 연 자리로 초점이 간다. 열린 동안 초점이 뒤 화면으로 나가지 않는다
WCAG 1.4.10 Reflow1280 미만은 시트로 바뀌고, 대화상자 높이는 화면의 80% 까지 — 넘치면 본문이 스크롤된다(머리 · 바닥이 화면 밖으로 나가지 않는다)
WCAG 2.5.8 Target Size — Minimum(AA ≥ 24×24)닫기 버튼 52 · 머리 ⋮ 36 · 바닥 버튼 36 ✓
WCAG 2.5.5 Target Size — Enhanced(AAA ≥ 44×44)닫기 버튼 52 ✓ · 머리 ⋮ · 바닥 버튼은 누르는 영역을 44 까지 넓힌다(Button) ✓
ARIArole="dialog" + aria-modal="true", 제목 aria-labelledby · 설명 aria-describedby(있을 때만), 닫기 버튼 이름 "닫기", 머리 ← 이름 "뒤로"(초점 차례 맨 앞), 머리 ⋮ 이름 "{항목 이름} 더보기"(제목 → 동작 → 닫기)

Do / Don't

✅ Do

  • 폼 · 상세는 한 부품으로 — 1280 이상 대화상자, 미만 시트.
  • 입력 폼은 바닥 [취소] [저장], 조회는 머리 닫기 버튼.
  • 입력 폼은 바깥 누르기로 닫지 않고, 바뀐 값이 있으면 닫기 전에 묻는다.
  • 긴 내용은 본문만 스크롤 — 머리 · 바닥은 늘 보인다.
  • 두 단계 흐름의 2단계는 머리 왼쪽 ← 로 1단계에 돌아간다(시트와 같은 값).

❌ Don't

  • 머리 닫기 버튼과 바닥 취소를 함께.
  • 되돌릴 수 없는 확인을 Dialog 로(Alert Dialog).
  • 높이 상한 없이 내용을 늘려 제목 · 버튼이 화면 밖으로.
  • 대화상자 위에 대화상자 — 단계가 많으면 페이지 · 단계로.

Specification

dialog.yaml 의 규칙을 하나도 빼지 않고 조건마다 그린다 — 웹 · 앱이 Dialog 를 만들 때 이 값을 그대로 쓴다. 조건이 없는 Base 가 모든 조합에 걸리고, 뒤의 규칙이 앞의 같은 값을 덮는다. 상태는 enabled 에서 바뀌는 값만 적었다. 1280 미만의 모습은 Bottom Sheet 의 Specification 이다.

Base

부위상태속성값
overlayenabled배경backgroundoverlay-dim-lightrgba(0, 0, 0, 0.50)다크 overlay-dim-darkrgba(0, 0, 0, 0.65)groo 딤(v102)
z-indexzIndex100z-modal — specs/z-index.md L2
rootenabled최대 높이maxHeight화면 높이의 80%dvh 우선 — 넘치면 본문만 스크롤
최대 너비maxWidth화면 폭 − 40좌우 20 은 남긴다
모서리radiusradius-r520px
배경backgroundbg-layer-floating#FFFFFF다크 #2D3346
그림자shadownone그림자 없이 딤과 표면 색으로 뜬다(SEED · Elevation)
z-indexzIndex101z-modal-content — specs/z-index.md L2
closeButtonenabled크기size52px투명 상자 — 보이는 건 아이콘 22
아이콘 크기iconSize22pxlucide x
모서리radiusradius-r312px
색colorfg-neutral-subtle#62697A다크 #A2A8B7
터치 영역touchTarget52 × 52
전환 대상transitionPropertybackground-color, scale
전환 시간transitionDurationmotion-duration-color-transition150ms
전환 곡선transitionEasingmotion-ease-easingcubic-bezier(0.35, 0, 0.35, 1)
위 거리top28px아이콘이 대화상자 위에서 28 · 오른쪽 24 — 제목 첫 줄 가운데와 맞는다
오른쪽 거리rightspacing-x624px
headerenabled좌우 여백paddingXspacing-x624px
위 여백paddingTopspacing-x624px
아래 여백paddingBottomspacing-x416px
간격gapspacing-x1_56px제목 ↔ 설명. 닫기 버튼이 있으면 오른쪽 52(24 + 아이콘 22 + 6)
titleenabled글자·아이콘foregroundfg-neutral#1A1F2E다크 #F5F6FA
글자typographytext-t822px / 30px
굵기fontWeight700
descriptionenabled글자·아이콘foregroundfg-neutral-muted#535866다크 #B7BDCC
글자typographytext-t516px / 22px
굵기fontWeight400
dividerenabled높이height1px
배경backgroundstroke-neutral-subtle#EDEFF3다크 #353B4D안쪽 그림자로 그린다 — 본문이 첫 자식이면(머리가 없으면) 그리지 않는다
전환 시간transitionDurationmotion-duration-color-transition150ms
전환 곡선transitionEasingmotion-ease-easingcubic-bezier(0.35, 0, 0.35, 1)
bodyenabled좌우 여백paddingXspacing-x624px
위 여백paddingTopspacing-x624px머리가 없을 때만 — 머리가 있으면 0
아래 여백paddingBottomspacing-x624px바닥이 없을 때만 — 바닥이 있으면 0
세로 넘침overflowYauto
footerenabled버튼 크기buttonSizesmall 36Button small(Button 결정)
좌우 여백paddingXspacing-x624px
위 여백paddingTopspacing-x416px
아래 여백paddingBottomspacing-x624px
간격gapspacing-x28px
가로 맞춤justifyContentflex-end오른쪽 정렬 — [취소] [저장]
dividerscrolled불투명도opacity1본문을 위로 스크롤하면 나타난다
closeButtonpressed배경backgroundbg-layer-floating-pressed#F7F8FD다크 #353B4DSEED bg.transparent-pressed 의 떠 있는 표면 짝(v102)
배율scale2px 거리 축소기준 52 → 0.962 — 모션 줄이기면 하지 않는다
focusRingfocused링 두께width2px
링 색colorstroke-focus-ringDesk#0147AD다크 #7AA9F6HR#075B3C다크 #2ABE84
링 간격offset2px

size=medium

부위상태속성값
rootenabled너비width480px

size=large

부위상태속성값
rootenabled너비width800px

headerAction=shown

부위상태속성값
headerActionenabled크기size36px
간격gapspacing-x14px닫기 아이콘 왼쪽 끝 ↔ 동작 단추 4. 닫기가 없는 입력 폼은 오른쪽 끝 — 단추 상자가 대화상자 오른쪽에서 16
터치 영역touchTarget44 × 44
이름name{항목 이름} 더보기아이콘만 있는 단추 — 항목 이름을 앞에 둔다
버튼buttonButton ghost iconOnly small보이는 36 · 아이콘 16 — 누르는 영역 44 × 44
centerYcenterY제목 첫 줄 가운데대화상자 위 24 + 줄 높이 30 의 절반 = 39 — 닫기 아이콘 가운데와 같다
focusOrderfocusOrder제목 → 동작 → 닫기DOM 차례 그대로 — ← 가 있으면 ← → 제목 → 동작 → 닫기
headerenabled오른쪽 여백paddingRight100px닫기 자리 52 + 사이 4 + 단추 36 + 8 — 제목 · 설명은 남은 폭에서 줄을 바꾼다. 닫기가 없는 입력 폼은 60(오른쪽 16 + 단추 36 + 8)

scrollFog=on

부위상태속성값
bodyenabled위 여백paddingTop20px머리가 있으면 20(머리 아래 16 은 그대로 두고 본문 안에 더한다), 머리가 없으면 24 그대로 — 흐림 깊이 이상
아래 여백paddingBottom80px바닥이 있어도 — 끝까지 내려도 마지막 줄이 흐림 아래 남지 않는다
스크롤 여유scrollPadding위 20px · 아래 80px키보드로 옮긴 칸이 흐림 아래 멈추지 않게
fogenabled크기size위 20px · 아래 80px본문 상자의 위 · 아래 끝
마스크maskgradient-fade-masklinear-gradient(#00000000 0%, #00000003 8%, #00000005 16%, #0000000d 22%, #00000014 29%, #00000021 35%, #0000002e 41%, #00000040 47%, #00000052 53%, #00000066 59%, #0000007a 65%, #00000094 71%, #000000ab 78%, #000000c7 84%, #000000e3 92%, #000000ff 100%)Scroll Fog overlayBody(scroll-fog.yaml) — 늘 켜져 있다. 넘쳤는지 재서 켜고 끄지 않는다

SEED 와 다른 점

  • 바뀌는 폭은 1280(사용자 결정 2026-10-02) — SEED Responsive Dialog 는 md(768). groo 는 Input Button 의 시트 · 팝오버 경계(1280)와 하나로 맞췄다.
  • 바닥 버튼은 Button small 36 — SEED 예제는 medium 40. Button 을 정할 때 "40 은 대화상자에 비해 굵다" 로 고른 값을 지켰다(2026-10-02 다시 확인).
  • 딤은 groo 0.50 · 다크 0.65(v102) — SEED 0.455.
  • 입력 폼은 바깥을 눌러도 닫히지 않고, 머리 닫기 버튼을 두지 않는다 — SEED 도 같은 쪽을 권한다(snippet 의 바깥 닫기 기본값 · "입력 폼에서는 헤더 X 지양"). groo 는 규칙으로 못박았다.
  • 반투명 색을 불투명 짝으로(v102) — 머리 아래 선 stroke-neutral-subtle, 닫기 버튼 누름 bg-layer-floating-pressed.
  • 본문 끝 흐림은 Scroll Fog(2026-10-03) — 넘칠 수 있는 본문에 늘 켜진 위 20 · 아래 80. SEED Dialog 는 본문이 넘칠 때만 아래 48 을 흐린다.
  • z-index 는 specs/z-index.md 의 L2(딤 z-modal 100 · 대화상자 z-modal-content 101) — 그 안에서 연 Popover(L3) · Alert Dialog(L5)가 위에 뜬다(SEED 는 모두 2 + layerIndex).

Migration notes

2026-10-30 — 머리 오른쪽 동작

구현 검수(I-20)에서 정해 웹 ds 가 먼저 만든 머리 ⋮ 자리를 적었다 — 시트와 같은 규칙이고, 자리 값만 대화상자의 닫기(52 상자 · 아이콘 22)에 맞췄다. dialog.yaml 에 축 headerAction · 부위 headerAction(Button ghost 아이콘만 36 · 닫기 아이콘 왼쪽 4 · 닫기가 없으면 오른쪽 16 · 제목 첫 줄 가운데 · 머리 오른쪽 100 / 60)을 더했다 — 웹 DialogContent.headerAction(67de7f1c)의 값이다. 레시피 DialogContent · ResponsiveDialogContent 에 headerAction 을 더했다. 새 토큰 없음. 옛 스펙은 dialog.history/v-pre-cleanup-1030.*.

2026-10-29 — 두 단계 흐름의 머리 ←

사용자 결정 17-1 Q3 = A — 한 부품 안 두 단계 흐름(자산 추가 — 종류 고르기 → 폼)의 2단계 머리 왼쪽 ← 를 1280 이상의 대화상자에도 같은 값으로 둔다. 고르지 않은 안 B(2단계 폼 맨 위 "종류" 칸)와 결정 전문은 Bottom Sheet 의 같은 날 Migration notes. 수치는 bottom-sheet.yaml 의 축 back 한 벌이고 dialog.yaml 은 고치지 않았다. 레시피 DialogContent · ResponsiveDialogContent 에 onBack 을 더했다.

2026-10-22 — 대화상자 위 대화상자의 갈 곳

Don't "대화상자 위에 대화상자" 는 그대로 두고 옛 문장의 대안 "단계가 많으면 페이지 · 단계로" 를 함께 적었다(남은 규칙 R18 — 그대로, https://claude.ai/artifact/Mag1tEv51MdQHdy1o8hY3L). 값 변경 없음.

2026-10-22 — Alert Dialog 와 견주는 표

SEED Dialog 문서의 "Dialog vs Alert Dialog" 네 줄에 groo 의 세 줄(바깥 · Esc · 1280 미만 모양 · 버튼)을 더한 표를 두었다 — 폰에서 Dialog 는 시트가 되고 확인창만 가운데에 남는 것이 SEED 에 없는 차이다(사용자 결정 1C — https://claude.ai/artifact/YRPbWzhgD4EDdKsVioU8Zj). 떠 있는 표면 일곱을 고르는 큰 표는 DESIGN.md 한 벌로(3C). 값 변경 없음.

2026-10-03 — 본문 끝 흐림을 Scroll Fog 로

사용자가 비교 페이지에서 끝 흐림을 SEED 의 늘 켜진 마스크(Scroll Fog)로 정해, 2026-10-02 의 "본문이 넘칠 때만 아래 48 흐림" 을 바꿨다 — 넘칠 수 있는 본문(목록 · 긴 폼)에 scrollFog 를 걸고, 걸면 위 20 · 아래 80 이 늘 흐리다(본문 안 여백도 그만큼). 넘친 쪽만 흐리는 안은 고르지 않았다. 상태 overflow 와 부위 fade 를 걷고 축 scrollFog · 부위 fog 를 두었다.

2026-10-02 — SEED Dialog 로 다시 정한다

사용자가 비교 페이지에서 정했다 — 일로 나누기(폼 · 상세는 한 부품이 폭으로 시트 ↔ 대화상자) · 경계 1280 · 입력 폼은 바깥으로 닫지 않음 · 닫기 버튼과 취소는 하나만 · SEED 모양(medium 480 · large 800 · 최대 80% · 모서리 20 · 제목 22 / 30 · 본문만 스크롤 + 흐림 · 머리 아래 선). 바닥 버튼은 Button 결정대로 small 36. 옛 Dialog(sm 420 · md 520 · lg 720 · 86vh · 모서리 12 · 제목 18 / 600 · 머리 X + 바닥 취소)는 걷었다 — 옛 스펙은 dialog.history/v-pre-seed-overlay.*.

제품은 앱 적용 단계에서 옮긴다(2026-10-02 조사).

  • Desk 웹 — ModalShell 53곳(768 에서 시트 ↔ 대화상자 → 1280). 열어도 초점이 안으로 들어가지 않고 Tab 이 가려진 뒤 화면을 돌며, 닫으면 초점이 body 로 떨어진다. ModalShell 의 aria-describedby 가 없는 id 를 가리킨다. 폼 33곳이 바깥 누르기로 경고 없이 닫힌다. 상세 바닥의 삭제(dangerSoft)는 4.01:1.
  • Desk 앱 — 시트가 대부분이고 프리셋 저장만 가운데 대화상자(PFormAlertDialog)다(→ 시트). 폼 29곳이 경고 없이 닫힌다.
  • HR 웹 — Dialog 30곳 중 18곳이 높이 상한이 없어 긴 내용이면 제목과 버튼이 화면 밖으로 나간다(데스크톱 y −161 · 모바일 y −671). 트리거 없이 연 21곳은 닫으면 초점이 body 로 떨어진다. 닫기 버튼 16×16 "Close", 딤 0.80, 설명 4.42:1.
  • 앱 적용 때 화면마다 정할 자리 — HR 전체 화면 Dialog 3(페이지 · 시트 · Side Panel), 데스크톱 필터(Dialog · Popover · Side Panel), 반복 거래 상세의 바닥 액션 3개.

이 페이지