setConfirmOpen(false)}
+ />
+
{meta.label}
diff --git a/frontend/src/features/resume/ui/ResumeList.tsx b/frontend/src/features/resume/ui/ResumeList.tsx
index f20e0599..cde47178 100644
--- a/frontend/src/features/resume/ui/ResumeList.tsx
+++ b/frontend/src/features/resume/ui/ResumeList.tsx
@@ -1,6 +1,7 @@
+import { useState } from 'react'
import { isApiError } from '@/shared/api'
import { useAnalysisProgress } from '@/shared/hooks'
-import { StatusBadge, type StatusTone } from '@/shared/ui'
+import { ConfirmDialog, StatusBadge, type StatusTone } from '@/shared/ui'
import { useDeleteResume, useResumes } from '../model/useResumes'
import { formatFileSize } from '../lib/format'
import type { Resume, ResumeStatus } from '../model/types'
@@ -54,6 +55,7 @@ function ResumeCard({
onDelete: () => void
}) {
const meta = STATUS_META[resume.status]
+ const [confirmOpen, setConfirmOpen] = useState(false)
const progress = useAnalysisProgress('RESUME', resume.id)
const showProgress =
!!progress &&
@@ -76,7 +78,7 @@ function ResumeCard({
+ setConfirmOpen(false)}
+ />
+
{meta.label}
diff --git a/frontend/src/shared/ui/ConfirmDialog/ConfirmDialog.test.tsx b/frontend/src/shared/ui/ConfirmDialog/ConfirmDialog.test.tsx
new file mode 100644
index 00000000..eeb7c3c9
--- /dev/null
+++ b/frontend/src/shared/ui/ConfirmDialog/ConfirmDialog.test.tsx
@@ -0,0 +1,63 @@
+import { describe, it, expect, vi } from 'vitest'
+import { render, screen } from '@testing-library/react'
+import userEvent from '@testing-library/user-event'
+import { ConfirmDialog } from './ConfirmDialog'
+
+const base = {
+ title: '삭제하시겠습니까?',
+ description: '되돌릴 수 없습니다.',
+ onConfirm: () => {},
+ onCancel: () => {},
+}
+
+describe('ConfirmDialog', () => {
+ it('open=false면 렌더되지 않는다', () => {
+ render()
+ expect(screen.queryByText('삭제하시겠습니까?')).toBeNull()
+ })
+
+ it('open이면 제목·설명을 보여준다', () => {
+ render()
+ expect(screen.getByText('삭제하시겠습니까?')).toBeTruthy()
+ expect(screen.getByText('되돌릴 수 없습니다.')).toBeTruthy()
+ })
+
+ it('확인/취소 버튼이 각각 콜백을 호출한다', async () => {
+ const onConfirm = vi.fn()
+ const onCancel = vi.fn()
+ render(
+ ,
+ )
+ await userEvent.click(screen.getByRole('button', { name: '삭제' }))
+ expect(onConfirm).toHaveBeenCalledOnce()
+ await userEvent.click(screen.getByRole('button', { name: '취소' }))
+ expect(onCancel).toHaveBeenCalledOnce()
+ })
+
+ it('loading이면 취소가 비활성화되고 확인은 막힌다', async () => {
+ const onConfirm = vi.fn()
+ render(
+ ,
+ )
+ expect(screen.getByRole('button', { name: '취소' })).toBeDisabled()
+ // loading 시 Spinner가 접근성 이름에 더해질 수 있어 부분 일치로 찾는다.
+ const confirm = screen.getByRole('button', { name: /삭제/ })
+ expect(confirm).toBeDisabled()
+ await userEvent.click(confirm)
+ expect(onConfirm).not.toHaveBeenCalled()
+ })
+})
diff --git a/frontend/src/shared/ui/ConfirmDialog/ConfirmDialog.tsx b/frontend/src/shared/ui/ConfirmDialog/ConfirmDialog.tsx
new file mode 100644
index 00000000..e35afc6b
--- /dev/null
+++ b/frontend/src/shared/ui/ConfirmDialog/ConfirmDialog.tsx
@@ -0,0 +1,53 @@
+import type { ReactNode } from 'react'
+import { Modal } from '../Modal'
+import { Button } from '../Button'
+
+export type ConfirmDialogProps = {
+ open: boolean
+ title: ReactNode
+ description?: ReactNode
+ confirmLabel?: string
+ cancelLabel?: string
+ danger?: boolean
+ loading?: boolean
+ onConfirm: () => void
+ onCancel: () => void
+}
+
+// 파괴적/되돌릴 수 없는 액션 전 확인을 받는 공용 다이얼로그.
+// Modal(ESC·포커스 복원·스크롤 락 내장) 위에 표준 취소/확인 푸터를 얹는다.
+export function ConfirmDialog({
+ open,
+ title,
+ description,
+ confirmLabel = '확인',
+ cancelLabel = '취소',
+ danger = false,
+ loading = false,
+ onConfirm,
+ onCancel,
+}: ConfirmDialogProps) {
+ return (
+ {} : onCancel}
+ title={title}
+ footer={
+
+
+
+
+ }
+ >
+ {description}
+
+ )
+}
diff --git a/frontend/src/shared/ui/ConfirmDialog/index.ts b/frontend/src/shared/ui/ConfirmDialog/index.ts
new file mode 100644
index 00000000..ab3f9465
--- /dev/null
+++ b/frontend/src/shared/ui/ConfirmDialog/index.ts
@@ -0,0 +1,2 @@
+export { ConfirmDialog } from './ConfirmDialog'
+export type { ConfirmDialogProps } from './ConfirmDialog'
diff --git a/frontend/src/shared/ui/index.ts b/frontend/src/shared/ui/index.ts
index c8b626a9..3f371790 100644
--- a/frontend/src/shared/ui/index.ts
+++ b/frontend/src/shared/ui/index.ts
@@ -2,3 +2,5 @@ export { StatusBadge } from './StatusBadge'
export type { StatusBadgeProps, StatusTone } from './StatusBadge'
export { Modal } from './Modal'
export type { ModalProps } from './Modal'
+export { ConfirmDialog } from './ConfirmDialog'
+export type { ConfirmDialogProps } from './ConfirmDialog'