모달 & 대화상자
규칙
- Radix Dialog 또는 shadcn
<Dialog>를 사용하세요 — 기본적으로 접근 가능하고, 포커스가 트래핑되며, 스크롤이 잠깁니다.
- 파괴적 작업에 대한 확인 대화상자: "이 프로젝트를 삭제하시겠습니까?"와 함께 취소(보조) 및 삭제(파괴적) 버튼을 제공합니다.
- 모바일용 시트/서랍: shadcn의
<Sheet>를 사용하세요 — 아래나 측면에서 슬라이드되며, 중앙 모달보다 터치 대상이 더 좋습니다.
- 포커스 트래핑: Radix가 자동으로 처리합니다 — 열릴 때 첫 번째 포커스 가능 요소에 포커스가 이동하고, 닫힐 때 돌아옵니다.
- 스크롤 잠금: 모달이 열려 있을 때 본문 스크롤을 방지합니다 — Radix가
<Dialog.Overlay>로 처리합니다.
- 닫기 트리거: Esc 키, 오버레이 외부 클릭, 명시적인 닫기 버튼 — 모두 Radix에서 제공합니다.
- 제어 상태:
open + onOpenChange props — 외부 이벤트로 트리거되는 모달의 경우 내부 상태에 의존하지 마세요.
- 대화상자 내 양식: Enter로 제출, 성공 시 닫기, 오류는 인라인으로 표시 — 유효성 검사 실패 시 닫지 마세요.
- 모바일: 작은 화면에서 작업을 위해 중앙 정렬
<Dialog> 대신 <Sheet> (하단 서랍)를 사용하세요.
패턴
import {
AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent,
AlertDialogDescription, AlertDialogFooter, AlertDialogHeader,
AlertDialogTitle, AlertDialogTrigger,
} from "@/components/ui/alert-dialog";
function DeleteButton({ onDelete }: { onDelete: () => Promise<void> }) {
const [loading, setLoading] = useState(false);
return (
<AlertDialog>
<AlertDialogTrigger asChild>
<Button variant="destructive">프로젝트 삭제</Button>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>이 프로젝트를 삭제하시겠습니까?</AlertDialogTitle>
<AlertDialogDescription>
이 작업은 프로젝트와 모든 데이터를 영구적으로 삭제하며 되돌릴 수 없습니다.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>취소</AlertDialogCancel>
<AlertDialogAction
onClick={async () => { setLoading(true); await onDelete(); setLoading(false); }}
disabled={loading}
>
{loading ? "삭제 중..." : "삭제"}
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
);
}
import { Sheet, SheetContent, SheetHeader, SheetTitle, SheetTrigger } from "@/components/ui/sheet";
function MobileMenu() {
return (
<Sheet>
<SheetTrigger asChild><Button variant="ghost" size="icon"><MenuIcon /></Button></SheetTrigger>
<SheetContent side="bottom">
<SheetHeader><SheetTitle>작업</SheetTitle></SheetHeader>
<nav className="flex flex-col gap-2 py-4">
<Button variant="ghost">편집</Button>
<Button variant="ghost">공유</Button>
<Button variant="destructive">삭제</Button>
</nav>
</SheetContent>
</Sheet>
);
}
피해야 할 사항
position: fixed와 수동 포커스 관리를 사용한 사용자 정의 모달 구축 — Radix/shadcn Dialog를 사용하세요.
- Esc 키 및 외부 클릭 시 닫기 기능 누락 — Radix가 둘 다 처리하므로 재정의하지 마세요.
- 모바일에서 작업 메뉴를 위해 중앙 정렬 모달 사용 — 엄지손가락이 닿기 쉬운 하단 Sheet/Drawer를 사용하세요.
- 양식 유효성 검사 실패 시 대화상자 닫기 — 열린 상태로 유지하고 인라인 오류를 표시하여 사용자가 수정하고 다시 제출하게 하세요.
- 적절한 쌓임 컨텍스트 없이 중첩 대화상자 사용 — 필요한 경우 적절한
z-index와 함께 Radix의 중첩 Dialog 지원을 사용하세요.