Dialog

Dialog

주 창이나 다른 다이얼로그 창 위에 겹쳐서, 아래 콘텐츠를 비활성(inert)으로 렌더링하는 창 컴포넌트예요.

출처: 문서

본문

현재 화면 위에 모달 창을 띄우는 다이얼로그 컴포넌트예요. 모달/비모달 모드를 모두 지원하고, 모달에서는 포커스가 자동으로 트랩되며 Esc 키로 닫혀요. 스크린 리더 공지는 Title/Description 컴포넌트로 관리해요.

Features

  • 모달/비모달 모드 지원.
  • 모달 내에서 포커스가 자동으로 트랩돼요.
  • 제어(controlled)/비제어(uncontrolled) 방식 모두 지원.
  • Title과 Description 컴포넌트로 스크린 리더 공지 관리.
  • Esc 키로 컴포넌트가 자동으로 닫혀요.

Anatomy

모든 파트를 임포트해 조립해요.

import { Dialog } from "radix-ui";

export default () => (
  <Dialog.Root>
    <Dialog.Trigger />

    <Dialog.Portal>
      <Dialog.Overlay />

      <Dialog.Content>
        <Dialog.Title />

        <Dialog.Description />

        <Dialog.Close />
      </Dialog.Content>
    </Dialog.Portal>
  </Dialog.Root>
);

API Reference

Root

다이얼로그의 모든 파트를 담아요.

Prop Type Default
defaultOpen boolean No default value
open boolean No default value
onOpenChange function No default value
modal boolean true

Trigger

다이얼로그를 여는 버튼이에요.

Prop Type Default
asChild boolean false
Data attribute Values
[data-state] "open" | "closed"

Portal

사용하면 overlay와 content 파트를 body로 포털해요.

Prop Type Default
forceMount boolean No default value
container HTMLElement document.body

Overlay

다이얼로그가 열려 있을 때 뷰의 비활성 부분을 덮는 레이어예요.

Prop Type Default
asChild boolean false
forceMount boolean No default value
Data attribute Values
[data-state] "open" | "closed"

Content

열린 다이얼로그에 렌더링할 콘텐츠를 담아요.

Prop Type Default
asChild boolean false
forceMount boolean No default value
onOpenAutoFocus function No default value
onCloseAutoFocus function No default value
onEscapeKeyDown function No default value
onPointerDownOutside function No default value
onInteractOutside function No default value
Data attribute Values
[data-state] "open" | "closed"

Close

다이얼로그를 닫는 버튼이에요.

Prop Type Default
asChild boolean false

Title

다이얼로그가 열렸을 때 공지될 접근 가능한 제목이에요.

제목을 숨기고 싶다면 Visually Hidden 유틸리티로 <VisuallyHidden asChild>처럼 감싸면 돼요.

Prop Type Default
asChild boolean false

Description

다이얼로그가 열렸을 때 공지될 선택적 접근 가능한 설명이에요.

설명을 숨기고 싶다면 Visually Hidden 유틸리티로 <VisuallyHidden asChild>처럼 감싸면 돼요. 설명을 완전히 제거하고 싶다면 이 파트를 빼고 Dialog.Content에 aria-describedby={undefined}를 전달하세요.

Prop Type Default
asChild boolean false

Examples

비동기 폼 제출 후 닫기

제어 props를 사용해 비동기 작업이 완료된 후 Dialog를 프로그래밍 방식으로 닫을 수 있어요.

import * as React from "react";

import { Dialog } from "radix-ui";

const wait = () => new Promise((resolve) => setTimeout(resolve, 1000));

export default () => {
  const [open, setOpen] = React.useState(false);

  return (
    <Dialog.Root open={open} onOpenChange={setOpen}>
      <Dialog.Trigger>Open</Dialog.Trigger>

      <Dialog.Portal>
        <Dialog.Overlay />

        <Dialog.Content>
          <form
            onSubmit={(event) => {
              wait().then(() => setOpen(false));
              event.preventDefault();
            }}
          >
            {/** some inputs */}

            <button type="submit">Submit</button>
          </form>
        </Dialog.Content>
      </Dialog.Portal>
    </Dialog.Root>
  );
};

스크롤 가능한 오버레이

콘텐츠를 overlay 안으로 옮겨 overflow가 있는 다이얼로그를 렌더링해요.

// index.jsx

import { Dialog } from "radix-ui";

import "./styles.css";

export default () => {
  return (
    <Dialog.Root>
      <Dialog.Trigger />

      <Dialog.Portal>
        <Dialog.Overlay className="DialogOverlay">
          <Dialog.Content className="DialogContent">...</Dialog.Content>
        </Dialog.Overlay>
      </Dialog.Portal>
    </Dialog.Root>
  );
};
/* styles.css */

.DialogOverlay {
  background: rgba(0 0 0 / 0.5);

  position: fixed;

  top: 0;

  left: 0;

  right: 0;

  bottom: 0;

  display: grid;

  place-items: center;

  overflow-y: auto;
}

.DialogContent {
  min-width: 300px;

  background: white;

  padding: 30px;

  border-radius: 4px;
}

커스텀 포털 컨테이너

다이얼로그가 포털될 요소를 커스터마이즈할 수 있어요.

import * as React from "react";

import { Dialog } from "radix-ui";

export default () => {
  const [container, setContainer] = React.useState(null);

  return (
    <div>
      <Dialog.Root>
        <Dialog.Trigger />

        <Dialog.Portal container={container}>
          <Dialog.Overlay />

          <Dialog.Content>...</Dialog.Content>
        </Dialog.Portal>
      </Dialog.Root>

      <div ref={setContainer} />
    </div>
  );
};

Accessibility

Dialog WAI-ARIA 디자인 패턴을 준수해요.

키보드 상호작용

Key Description
Space Opens/closes the dialog.
Enter Opens/closes the dialog.
Tab Moves focus to the next focusable element.
Shift + Tab Moves focus to the previous focusable element.
Esc Closes the dialog and moves focus to Dialog.Trigger.

Custom APIs

primitive 파트를 자신의 컴포넌트로 추상화해 나만의 API를 만들 수 있어요.

Overlay와 닫기 버튼 추상화

이 예제는 Dialog.Overlay와 Dialog.Close 파트를 추상화해요.

사용법

import { Dialog, DialogTrigger, DialogContent } from "./your-dialog";

export default () => (
  <Dialog>
    <DialogTrigger>Dialog trigger</DialogTrigger>

    <DialogContent>Dialog Content</DialogContent>
  </Dialog>
);

구현

// your-dialog.jsx

import * as React from "react";

import { Dialog as DialogPrimitive } from "radix-ui";

import { Cross1Icon } from "@radix-ui/react-icons";

export const DialogContent = React.forwardRef(({ children, ...props }, forwardedRef) => (
  <DialogPrimitive.Portal>
    <DialogPrimitive.Overlay />

    <DialogPrimitive.Content {...props} ref={forwardedRef}>
      {children}

      <DialogPrimitive.Close aria-label="Close">
        <Cross1Icon />
      </DialogPrimitive.Close>
    </DialogPrimitive.Content>
  </DialogPrimitive.Portal>
));

export const Dialog = DialogPrimitive.Root;

export const DialogTrigger = DialogPrimitive.Trigger;

더 알아보기 (Learn more)

  • modal prop을 false로 하면 비모달 다이얼로그(팝오버처럼)로 동작해요.
  • Dialog.Content를 Dialog.Overlay 안에 배치하면 오버레이를 스크롤 컨테이너로 활용한 스크롤 가능한 다이얼로그를 만들 수 있어요.