# Dialog
URL: /react/components/dialog
Source: https://github.com/daangn/seed-design/blob/dev/docs/content/react/components/dialog.mdx
화면 중앙에 떠서 사용자의 주의를 모으는 다이얼로그입니다. 제목, 본문, 푸터와 닫기 버튼을 갖추어 폼이나 스크롤되는 콘텐츠 등 풍부한 내용을 담을 때 사용합니다.
사용 가능 버전: @seed-design/react@2.1.0, @seed-design/css@2.3.0
## Preview
```tsx
import { HStack, Text } from "@seed-design/react";
import {
DialogAction,
DialogBody,
DialogContent,
DialogFooter,
DialogRoot,
DialogTrigger,
} from "seed-design/ui/dialog";
import { ActionButton } from "seed-design/ui/action-button";
const DialogPreview = () => {
return (
Open Dialog
본문에는 사용자가 확인해야 할 내용이나 추가 입력 폼을 배치할 수 있습니다.
취소
확인
);
};
export default DialogPreview;
```
사용자의 확인이나 경고가 목적이라면 Alert Dialog를 사용하세요.
## Installation
### Default
화면 중앙에 떠서 제목, 본문, 푸터를 담는 기본 Dialog 컴포넌트를 포함합니다.
- npm: npx @seed-design/cli@latest add ui:dialog
- pnpm: pnpm dlx @seed-design/cli@latest add ui:dialog
- yarn: yarn dlx @seed-design/cli@latest add ui:dialog
- bun: bun x @seed-design/cli@latest add ui:dialog
### Responsive
뷰포트에 따라 Bottom Sheet로 자동 전환되는 반응형 변형입니다.
- npm: npx @seed-design/cli@latest add ui:responsive-dialog
- pnpm: pnpm dlx @seed-design/cli@latest add ui:responsive-dialog
- yarn: yarn dlx @seed-design/cli@latest add ui:responsive-dialog
- bun: bun x @seed-design/cli@latest add ui:responsive-dialog
## Props
### `DialogRoot`
### `DialogTrigger`
### `DialogContent`
### `DialogBody`
### `DialogFooter`
### `DialogAction`
### `ResponsiveDialogRoot`
## Examples
### Trigger
``는 `aria-haspopup="dialog"` 속성을 설정하고, Dialog의 `open` 상태에 따라 `aria-expanded` 속성을 자동으로 설정합니다. 이 속성은 스크린 리더와 같은 보조 기술에 유용합니다.
```tsx
import { HStack, Text } from "@seed-design/react";
import {
DialogAction,
DialogBody,
DialogContent,
DialogFooter,
DialogRoot,
DialogTrigger,
} from "seed-design/ui/dialog";
import { ActionButton } from "seed-design/ui/action-button";
const DialogTriggerExample = () => {
return (
Open
Trigger를 클릭하면 현재 화면 위에 Dialog가 열립니다.
취소
확인
);
};
export default DialogTriggerExample;
```
### Controlled
Trigger 외의 방식으로 Dialog를 열고 닫을 수 있습니다. 이 경우 `open` prop을 사용하여 Dialog의 상태를 제어합니다.
```tsx
import { HStack, Text } from "@seed-design/react";
import { useState } from "react";
import { ActionButton } from "seed-design/ui/action-button";
import {
DialogAction,
DialogBody,
DialogContent,
DialogFooter,
DialogRoot,
} from "seed-design/ui/dialog";
const DialogControlled = () => {
const [open, setOpen] = useState(false);
return (
<>
setOpen(true)}>
열기
Labore do culpa dolore irure nisi dolor dolor laboris veniam ipsum excepteur
adipisicing laboris non quis. Velit ea ut minim. Magna dolore culpa velit incididunt
consequat sint. Fugiat ad culpa labore dolore esse dolore ex aliquip duis aute aliquip
ad velit et.
취소
확인
>
);
};
export default DialogControlled;
```
### Size
``에 `size` prop을 설정하여 Dialog의 너비를 변경할 수 있습니다. `"medium"` (480px, 기본값)과 `"large"` (800px)를 지원합니다.
`md` 미만 화면에서는 뷰포트 너비의 90%를 차지하고, `md` 이상에서 각 size의 최대 너비가 적용됩니다. content의 높이는 뷰포트의 80%로 제한됩니다.
``에 `width`, `maxWidth` prop을 전달하여 너비를 직접 제어할 수도 있습니다.
프리셋 size 대신 뷰포트 기반의 유동적인 너비가 필요할 때 사용합니다.
단일 값은 모든 breakpoint에 적용되므로, md 미만의 기본 너비(뷰포트의 90%)를 유지하려면 `width={{ md: "560px" }}`처럼 breakpoint 객체로 전달하세요.
```tsx
import { Flex, HStack, Text, VStack } from "@seed-design/react";
import { useState } from "react";
import { ActionButton } from "seed-design/ui/action-button";
import {
DialogAction,
DialogBody,
DialogContent,
DialogFooter,
DialogRoot,
DialogTrigger,
} from "seed-design/ui/dialog";
import { Slider } from "seed-design/ui/slider";
const DialogSize = () => {
const [width, setWidth] = useState(90);
const [maxWidth, setMaxWidth] = useState(640);
return (
Medium (480px)
기본 너비로 상세 정보와 주요 액션을 함께 제공합니다.
취소
확인
Large (800px)
넓은 다이얼로그에서 더 많은 폼 필드나 상세 콘텐츠를 다룹니다.
취소
확인
Custom (조절 가능)
뷰포트 너비에 따라 유동적으로 커지되 maxWidth로 최대 크기를 제한합니다.
setWidth(values[0])}
/>
setMaxWidth(values[0])}
/>
취소
확인
);
};
export default DialogSize;
```
### Body
``는 본문 영역을 스크롤 가능하게 만듭니다. 본문이 길어 스크롤되면 헤더 아래에 구분선이 나타나고, 하단은 서서히 사라지는 마스크가 적용됩니다.
Body가 뷰포트 높이를 넘겨 스크롤이 생길 때에만 하단 fade 마스크와 `padding-bottom`이 적용됩니다. 본문이 짧아 넘치지 않으면 마스크가 적용되지 않아 마지막 줄이 흐려지지 않습니다.
```tsx
import { HStack, Text, VStack } from "@seed-design/react";
import { ActionButton } from "seed-design/ui/action-button";
import {
DialogAction,
DialogBody,
DialogContent,
DialogFooter,
DialogRoot,
DialogTrigger,
} from "seed-design/ui/dialog";
const DialogBodyExample = () => {
return (
짧은 본문 (fade 없음)
내용이 짧아 스크롤이 없으면 하단 마스크가 적용되지 않아, 마지막 줄이 흐려지지
않습니다.
취소
확인
긴 본문 (fade 적용)
{Array.from({ length: 16 }, (_, index) => (
{index + 1}. Body가 넘치면 하단에 fade 마스크와 padding-bottom이 적용되고,
스크롤하면 헤더 아래에 구분선이 나타납니다.
))}
취소
확인
);
};
export default DialogBodyExample;
```
### Custom Body
``에 `paddingX`, `minHeight`, `maxHeight`, `justifyContent`, `alignItems` prop을 전달하여 본문 영역을 직접 제어할 수 있습니다.
기본 캡(뷰포트의 80%)보다 낮게 스크롤 높이를 제한하거나, 짧은 내용에서도 높이를 고정하거나, 가로 패딩을 제거해 콘텐츠를 가장자리까지 배치할 때 사용합니다.
```tsx
import { Box, Flex, HStack, Text, VStack } from "@seed-design/react";
import { ActionButton } from "seed-design/ui/action-button";
import {
DialogAction,
DialogBody,
DialogContent,
DialogFooter,
DialogRoot,
DialogTrigger,
} from "seed-design/ui/dialog";
const DialogCustomBody = () => {
return (
maxHeight 200px
{Array.from({ length: 12 }, (_, index) => (
{index + 1}. 본문이 200px을 넘으면 그 안에서 스크롤됩니다.
))}
취소
확인
minHeight + 가운데 정렬
아직 항목이 없습니다
취소
추가
paddingX 0
가장자리까지 닿는 영역입니다
취소
확인
);
};
export default DialogCustomBody;
```
### Show Close Button
``에 `showCloseButton` prop을 전달하여 우측 상단 닫기 버튼을 표시할 수 있습니다.
기본 값은 `true`입니다.
```tsx
import { Flex, HStack, Text } from "@seed-design/react";
import { ActionButton } from "seed-design/ui/action-button";
import {
DialogAction,
DialogBody,
DialogContent,
DialogFooter,
DialogRoot,
DialogTrigger,
} from "seed-design/ui/dialog";
const DialogShowCloseButton = () => {
return (
닫기 버튼 있음
기본적으로 우측 상단에 닫기 버튼이 표시됩니다.
취소
확인
닫기 버튼 없음
닫기 버튼을 숨길 때는 본문이나 푸터에 닫을 수 있는 액션을 제공하세요.
취소
확인
);
};
export default DialogShowCloseButton;
```
### Footer Layout
`DialogFooter`는 flex 레이아웃만 제공하며, 버튼 배치는 `VStack`, `HStack` 등으로 직접 구성합니다.
```tsx
import { HStack, Text, VStack } from "@seed-design/react";
import { ActionButton } from "seed-design/ui/action-button";
import {
DialogAction,
DialogBody,
DialogContent,
DialogFooter,
DialogRoot,
DialogTrigger,
} from "seed-design/ui/dialog";
const DialogFooterLayout = () => {
return (
Footer 레이아웃
DialogFooter는 flex 레이아웃만 제공합니다.
넓은 다이얼로그에서는 주요 액션을 우측에 가로로 정렬할 수 있습니다.
취소
확인
);
};
export default DialogFooterLayout;
```
### Prevent Close
`DialogAction`의 `onClick`에서 `e.preventDefault()`를 호출하면 다이얼로그가 닫히지 않습니다.
```tsx
import { HStack } from "@seed-design/react";
import { useState } from "react";
import { ActionButton } from "seed-design/ui/action-button";
import {
DialogAction,
DialogBody,
DialogContent,
DialogFooter,
DialogRoot,
DialogTrigger,
} from "seed-design/ui/dialog";
import { Switch } from "seed-design/ui/switch";
const DialogPreventClose = () => {
const [preventClose, setPreventClose] = useState(true);
return (
열기
취소
{
if (preventClose) {
e.preventDefault();
}
}}
>
확인
);
};
export default DialogPreventClose;
```
### `onOpenChange` Details
`onOpenChange` 두 번째 인자로 `details`가 제공됩니다.
#### `reason`
**열릴 때** (`open: true`)
- `"trigger"`: `DialogTrigger`로 열림
**닫힐 때** (`open: false`)
- `"closeButton"`: `DialogAction` 또는 우측 상단 닫기 버튼으로 닫힘
- `"escapeKeyDown"`: ESC 키 사용
- `"interactOutside"`: 외부 영역 클릭
- `DialogRoot`는 기본적으로 `closeOnInteractOutside={false}`입니다. `interactOutside`는 이 옵션을 `true`로 설정한 경우에만 발생할 수 있습니다.
- `"cascadeDismiss"`: 상위 레이어 닫힘으로 인한 연쇄 닫힘
```tsx
import { HStack, Text, VStack } from "@seed-design/react";
import { useState } from "react";
import { ActionButton } from "seed-design/ui/action-button";
import {
DialogAction,
DialogBody,
DialogContent,
DialogFooter,
DialogRoot,
DialogTrigger,
} from "seed-design/ui/dialog";
import { Switch } from "seed-design/ui/switch";
const DialogOnOpenChangeReason = () => {
const [open, setOpen] = useState(false);
const [openReason, setOpenReason] = useState(null);
const [closeReason, setCloseReason] = useState(null);
const [closeOnInteractOutside, setCloseOnInteractOutside] = useState(false);
return (
{
setOpen(open);
(open ? setOpenReason : setCloseReason)(details?.reason ?? null);
}}
>
열기
ESC 키를 누르거나 우측 상단 닫기 버튼, 하단 버튼을 눌러 닫아보세요. 아래 스위치를 켠
뒤 바깥 영역을 누르면 interactOutside 이유로 닫힙니다.
취소
확인
마지막 열림 이유: {openReason ?? "-"}
마지막 닫힘 이유: {closeReason ?? "-"}
);
};
export default DialogOnOpenChangeReason;
```
#### Confirm Before Close
작성 중인 내용을 실수로 닫는 것을 막고 싶다면, `open`을 제어 상태로 두고 특정 `reason`일 때 `open`을 유지한 채 확인용 Alert Dialog를 띄워 사용자에게 되물을 수 있습니다.
```tsx
import { HStack, ResponsivePair, Text, VStack } from "@seed-design/react";
import { useState } from "react";
import { ActionButton } from "seed-design/ui/action-button";
import {
AlertDialogAction,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogRoot,
AlertDialogTitle,
} from "seed-design/ui/alert-dialog";
import {
DialogAction,
DialogBody,
DialogContent,
DialogFooter,
DialogRoot,
DialogTrigger,
type DialogRootProps,
} from "seed-design/ui/dialog";
import { Switch } from "seed-design/ui/switch";
const closeReasons = [
"closeButton",
"escapeKeyDown",
"interactOutside",
] as const satisfies NonNullable<
Parameters>[1]
>["reason"][];
const confirmReasonLabels = {
closeButton: "닫기 버튼으로 닫을 때 확인 (closeButton)",
escapeKeyDown: "ESC 키로 닫을 때 확인 (escapeKeyDown)",
interactOutside: "바깥 클릭으로 닫을 때 확인 (interactOutside)",
} as const satisfies Record<(typeof closeReasons)[number], string>;
const DialogConfirmBeforeClose = () => {
const [open, setOpen] = useState(false);
const [confirmOpen, setConfirmOpen] = useState(false);
const [closeOnInteractOutside, setCloseOnInteractOutside] = useState(true);
const [confirmReasons, setConfirmReasons] = useState<
Record<(typeof closeReasons)[number], boolean>
>({ closeButton: true, escapeKeyDown: true, interactOutside: true });
return (
<>
{
if (nextOpen) {
setOpen(true);
return;
}
const reason = closeReasons.find((reason) => reason === details?.reason);
if (reason && confirmReasons[reason]) {
setConfirmOpen(true);
return;
}
setOpen(false);
}}
>
작성 폼 열기
닫으려는 reason별로 확인 다이얼로그 띄우기
{closeReasons.map((reason) => (
setConfirmReasons((prev) => ({ ...prev, [reason]: checked }))
}
/>
))}
취소
저장
정말 닫을까요?
작성 중인 내용은 저장되지 않습니다.
setConfirmOpen(false)}>
계속 작성
{
setConfirmOpen(false);
setOpen(false);
}}
>
닫기
>
);
};
export default DialogConfirmBeforeClose;
```
### Portalled
Portal은 기본적으로 `document.body`에 렌더링됩니다.
```tsx
import { HStack, Portal, Text } from "@seed-design/react";
import { ActionButton } from "seed-design/ui/action-button";
import {
DialogAction,
DialogBody,
DialogContent,
DialogFooter,
DialogRoot,
DialogTrigger,
} from "seed-design/ui/dialog";
const DialogPortalled = () => {
return (
// You can set z-index dialog with "--layer-index" custom property. useful for stackflow integration.
열기
Portal은 기본적으로 document.body에 렌더링됩니다.
취소
확인
);
};
export default DialogPortalled;
```
### Responsive
`ResponsiveDialog`를 사용하면 md 이상에서는 Dialog, sm 이하에서는 Bottom Sheet로 자동 전환됩니다. 뷰포트를 줄여서 전환 동작을 확인해보세요.
`onOpenChange`는 열림 상태만 전달하며, Dialog 또는 Bottom Sheet에만 적용되는 Root 옵션은 `dialogRootProps`, `bottomSheetRootProps`로 전달합니다.
```tsx
import { HStack, Text, useResponsiveDialogContext } from "@seed-design/react";
import { forwardRef } from "react";
import { ActionButton } from "seed-design/ui/action-button";
import {
ResponsiveDialogAction,
ResponsiveDialogBody,
ResponsiveDialogContent,
ResponsiveDialogFooter,
ResponsiveDialogRoot,
ResponsiveDialogTrigger,
type ResponsiveDialogFooterProps,
} from "seed-design/ui/responsive-dialog";
const Footer = forwardRef((props, ref) => {
const { shouldUseBottomSheet } = useResponsiveDialogContext();
return (
취소
확인
);
});
const DialogResponsive = () => {
return (
Open
md 이상에서는 화면 중앙의 Dialog로, sm 이하에서는 화면 하단에서 슬라이드되는 Bottom
Sheet로 표시됩니다.
);
};
export default DialogResponsive;
```