본문으로 건너뛰기

버그 제보

이상한 점을 알려 주시면 고칠게요. 지금 보고 있는 페이지와 브라우저 정보가 함께 담겨요.

페이지:

Frontend

MUI Dialog로 삭제 확인 창 만들기

작성 2026. 10. 13.추가
목차6개 항목

삭제처럼 되돌릴 수 없는 동작은 클릭 한 번에 바로 실행하지 않고 확인 창을 거치게 한다. MUI의 Dialog로 확인 창을 컴포넌트 하나로 만들어 두면 여러 화면에서 재사용할 수 있다.

흐름

flowchart LR
  click["삭제 클릭"] --> open["다이얼로그 열기"]
  open -->|"확인"| api["API 요청"]
  api --> done["성공 시 알림, 목록 갱신, 목록 페이지로 이동"]
  open -->|"취소"| close["다이얼로그만 닫기"]

UX: 위험한 버튼은 덜 눈에 띄게

사용자가 실수로 누르기 쉬운 쪽이 안전한 동작이 되도록 한다. 삭제 버튼은 빨간색이지만 테두리만 있는 outlined 스타일로, 취소 버튼은 채워진 contained 스타일로 만든다. "빨간색 = 강조"가 아니라 "빨간색 = 위험 표시"로 쓰는 것이다. 버튼 문구도 "확인"보다 "네, 삭제합니다"처럼 결과를 그대로 적으면 실수가 줄어든다.

컴포넌트

import Button from "@mui/material/Button"
import Dialog from "@mui/material/Dialog"
import DialogActions from "@mui/material/DialogActions"
import DialogContent from "@mui/material/DialogContent"
import DialogContentText from "@mui/material/DialogContentText"
import DialogTitle from "@mui/material/DialogTitle"
 
interface DeleteConfirmDialogProps {
  open: boolean
  onClose: () => void
  onConfirm: () => void
}
 
export default function DeleteConfirmDialog({ open, onClose, onConfirm }: DeleteConfirmDialogProps) {
  return (
    <Dialog
      open={open}
      onClose={onClose}
      aria-labelledby="delete-dialog-title"
      aria-describedby="delete-dialog-description"
    >
      <DialogTitle id="delete-dialog-title">정말 삭제하시겠습니까?</DialogTitle>
      <DialogContent>
        <DialogContentText id="delete-dialog-description">
          삭제한 뒤에는 복구할 수 없습니다.
        </DialogContentText>
      </DialogContent>
      <DialogActions sx={{ minWidth: 300, px: 2, py: 1.5 }}>
        <Button onClick={onClose} variant="contained" color="inherit" disableElevation>
          취소
        </Button>
        <Button onClick={onConfirm} variant="outlined" color="error">
          네, 삭제합니다
        </Button>
      </DialogActions>
    </Dialog>
  )
}
  • aria-labelledby, aria-describedby로 제목·설명을 연결해 스크린 리더가 다이얼로그 내용을 읽을 수 있게 한다.
  • props는 open/onClose/onConfirm처럼 MUI 관례에 맞춘 이름을 쓰면 다른 MUI 컴포넌트와 사용법이 같아진다. setter를 통째로 넘기는 것보다 의도가 분명하다.

사용하는 쪽

const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false)
 
const handleDelete = async () => {
  const res = await removeItem(id)
  if (res.data) {
    setIsDeleteDialogOpen(false)
    refresh?.()
    openAlert({ message: "삭제되었습니다." })
    navigate("/item/list")
  }
}
 
<Button color="error" onClick={() => setIsDeleteDialogOpen(true)}>삭제</Button>
<DeleteConfirmDialog
  open={isDeleteDialogOpen}
  onClose={() => setIsDeleteDialogOpen(false)}
  onConfirm={handleDelete}
/>

아이콘 버튼

글자 없이 아이콘만 있는 버튼은 MUI IconButton을 쓰고, 화면 낭독기가 읽을 이름이 없으므로 aria-label을 꼭 준다.

핵심 정리

  • 되돌릴 수 없는 동작 앞에는 확인 다이얼로그를 둔다.
  • 위험한 버튼은 outlined, 안전한 버튼은 contained로 시각적 무게를 반대로 준다.
  • 다이얼로그는 open/onClose/onConfirm props를 받는 재사용 컴포넌트로 만든다.

댓글

GitHub 계정으로 댓글을 남길 수 있어요.