본문으로 건너뛰기
오버레이 / dialog

Dialog

포커스를 가두는 모달 다이얼로그 (Radix Dialog 기반).
오버레이dialogStatic specimendialog다이얼로그모달modal
01 · Specimen

먼저 보고, 그다음 계약을 읽습니다

포커스를 가두는 모달 다이얼로그 (Radix Dialog 기반).

Workbench 불러오는 중…
Component intent의도와 경계 읽기

Radix Dialog 위에 .axe-dialog__* 스타일을 입힌 wrapper 다. Dialog(Root)·DialogTrigger·DialogContent·DialogTitle·DialogDescription·DialogClose 를 조합해 쓰고, DialogHeader/DialogFooter 는 title·설명·액션 라인을 잡아주는 선택적 레이아웃 헬퍼다. 포커스 트랩·Esc 닫기·바깥 클릭 닫기·ARIA role/labelledby/describedby·바깥 트리 aria-hidden 격리는 전부 Radix 가 처리하고, @axe/ui 는 size 변형과 표면 스타일만 담당한다. 닫기 X 버튼은 자동으로 그려지지 않으므로 DialogClose 를 직접 배치한다(헤더는 우상단 X 자리를 위해 padding-right 를 비워둔다).

02 · Use

예제

SSOT에 등록된 실제 API 기준 snippet입니다. standalone 배지만 독립 실행 단위이며, fragment는 주변 state·handler 문맥을 생략합니다.

01기본 (확인 다이얼로그)tsxfragment
기본 (확인 다이얼로그)
import {  Dialog,  DialogTrigger,  DialogContent,  DialogHeader,  DialogTitle,  DialogDescription,  DialogFooter,  DialogClose,  Button,} from "@axe/ui";<Dialog>  <DialogTrigger asChild>    <Button>초대 보내기</Button>  </DialogTrigger>  <DialogContent size="sm">    <DialogHeader>      <DialogTitle>멤버 초대</DialogTitle>      <DialogDescription>        입력한 이메일로 워크스페이스 초대 링크가 발송됩니다.      </DialogDescription>    </DialogHeader>    <DialogFooter>      <DialogClose asChild>        <Button variant="ghost">취소</Button>      </DialogClose>      <Button variant="primary" onClick={sendInvite}>보내기</Button>    </DialogFooter>  </DialogContent></Dialog>
02controlled + 전체화면tsxfragment
controlled + 전체화면
import { Dialog, DialogContent, DialogTitle } from "@axe/ui";<Dialog open={open} onOpenChange={setOpen}>  <DialogContent size="full">    <DialogTitle>문서 뷰어</DialogTitle>    {/* … 전체화면 콘텐츠 … */}  </DialogContent></Dialog>
03CSS-only 정적 표면 (비-React)htmlfragment
CSS-only 정적 표면 (비-React)
<div class="axe-dialog__overlay"></div><div class="axe-dialog__content axe-dialog__content--sm" role="dialog" aria-modal="true" aria-labelledby="dlg-t">  <div class="axe-dialog__header">    <h2 id="dlg-t" class="axe-dialog__title">멤버 초대</h2>    <p class="axe-dialog__description">초대 링크가 발송됩니다.</p>  </div>  <div class="axe-dialog__footer">    <button class="axe-btn axe-btn--ghost">취소</button>    <button class="axe-btn axe-btn--primary">보내기</button>  </div></div>
03 · React

Props

TSX 소스가 진실입니다. 주요 export 컴포넌트의 public API만 노출합니다.

이름타입필수기본값설명
openboolean열림 상태 (controlled). Radix Dialog.Root prop.
onOpenChange(open: boolean) => void열림/닫힘 전환 콜백 (Root).
defaultOpenbooleanfalseuncontrolled 초기 열림 상태 (Root).
modalbooleantruetrue 면 바깥과의 상호작용을 차단하고 포커스를 가둔다 (Root).
size"sm" | "md" | "lg" | "xl" | "full""md"DialogContent 의 max-width. sm=440 / md=560 / lg=720 / xl=960 / full=100vw(전체화면·radius 0).
withOverlaybooleantrueDialogContent 가 흐린 backdrop 오버레이를 함께 렌더할지. false 면 콘텐츠만 뜬다.
...rest (DialogContent)React.ComponentPropsWithoutRef<typeof Dialog.Content>onEscapeKeyDown·onPointerDownOutside·forceMount 등 Radix Content props 를 그대로 전파.
...rest (Header/Footer)React.HTMLAttributes<HTMLDivElement>DialogHeader·DialogFooter 는 className 병합 후 나머지 속성을 루트 <div> 로 전파.
04 · Any stack

.axe-* 클래스 계약

React 밖에서도 같은 표면을 그리는 공개 계약입니다. stable은 minor 버전 안에서 이름을 바꾸지 않습니다.

클래스안정성용도
.axe-dialog__overlaystable화면 전체를 덮는 흐린 backdrop(fixed inset:0).
.axe-dialog__contentstable중앙 정렬 카드 표면(fixed·translate 중앙).
.axe-dialog__content--smstablemax-width 440px 변형.
.axe-dialog__content--mdstablemax-width 560px 변형(기본).
.axe-dialog__content--lgstablemax-width 720px 변형.
.axe-dialog__content--xlstablemax-width 960px 변형.
.axe-dialog__content--fullstable전체화면 변형(radius 0·100dvh).
.axe-dialog__headerstabletitle+설명 스택. 우상단 close X 자리로 padding-right 확보.
.axe-dialog__titlestable제목(display 폰트·semibold).
.axe-dialog__descriptionstable보조 설명 문단(secondary 톤).
.axe-dialog__footerstable우정렬 액션 버튼 라인(상단 구분선).
비-React 소비 노트
정적 열림 표면(오버레이+카드)은 .axe-dialog__* 마크업만으로 재현 가능하다. 다만 포털·포커스 트랩·Esc/바깥클릭 닫기·닫힘 애니메이션(data-state=closed)은 Radix 런타임이 만들므로, 실제 상호작용 다이얼로그는 React 컴포넌트로 소비해야 한다.
05 · Inclusive

접근성

키보드, ARIA, 구현 노트를 함께 검토합니다.

Keyboard
  • Esc 로 닫기(트리거로 포커스 복원)
  • Tab/Shift+Tab 은 콘텐츠 안에서만 순환(포커스 트랩)
  • 열릴 때 첫 포커스 대상으로 자동 이동, 닫힐 때 트리거로 복원
ARIA

Radix 가 콘텐츠에 role=dialog 를 부여하고, DialogTitle 을 aria-labelledby, DialogDescription 을 aria-describedby 로 자동 연결한다. 모달 격리는 aria-modal 속성이 아니라 바깥 트리 전체에 aria-hidden 을 거는 방식(aria-hidden 패키지의 hideOthers)으로 구현된다 — 설치된 @radix-ui/react-dialog 는 콘텐츠에 aria-modal 을 붙이지 않는다. 트리거에는 aria-haspopup=dialog·aria-expanded·aria-controls 가 붙는다.

Notes

DialogTitle 은 접근성상 필수 — 생략하면 Radix 가 개발 콘솔에 경고한다. 시각적으로 숨기려면 sr-only 로. modal=true 기본이라 바깥 스크롤이 잠기고 포인터가 차단된다. data-state=open|closed 로 진입/퇴장 애니메이션이 갈리며 prefers-reduced-motion 시 애니메이션이 제거된다.

06 · Judgment

권장 · 지양

권장
  • DialogTitle 을 항상 넣을 것(접근성 라벨) — 숨기려면 sr-only.
  • 닫기 경로(취소/X)를 명시적으로 제공하고 DialogClose 로 감쌀 것.
  • 파괴적 확인(삭제 등)에는 Dialog 대신 AlertDialog 를 쓸 것.
지양
  • size=full 을 작은 확인 모달에 쓰지 말 것 — 콘텐츠 밀도에 맞춰.
  • DialogContent 안에 또 다른 포커스 트랩(중첩 모달)을 임의로 겹치지 말 것.
검색창을 열면 컴포넌트 인덱스를 불러옵니다.