Confirmation Dialog

A centered modal with backdrop, title, description, and Cancel/Confirm actions. Supports a destructive variant with a red Confirm button and warning icon. Esc-to-dismiss and backdrop-click both close the dialog. Inspired by Linear.

Modals & DialogsInspired by Linear
Live PreviewInteractive
Linear · Project actions

Project settings

Click a trigger to open · Backdrop or Esc dismisses · Red Confirm marks destructive actions

ConfirmationDialog.tsx
// Dependencies: react ^18, lucide-react
import React, { useEffect } from 'react';
import { X, AlertTriangle } from 'lucide-react';

function cn(...classes: (string | false | null | undefined)[]) {
  return classes.filter(Boolean).join(' ');
}

type Props = {
  open: boolean;
  onClose: () => void;
  onConfirm: () => void;
  title: string;
  description: string;
  destructive?: boolean;
  confirmLabel?: string;
  cancelLabel?: string;
};

export function ConfirmationDialog({
  open,
  onClose,
  onConfirm,
  title,
  description,
  destructive = false,
  confirmLabel = 'Confirm',
  cancelLabel = 'Cancel',
}: Props) {
  useEffect(() => {
    if (!open) return;
    const handleKey = (e: KeyboardEvent) => {
      if (e.key === 'Escape') onClose();
    };
    window.addEventListener('keydown', handleKey);
    return () => window.removeEventListener('keydown', handleKey);
  }, [open, onClose]);

  if (!open) return null;

  return (
    <div
      role="dialog"
      aria-modal="true"
      aria-labelledby="confirmation-dialog-title"
      className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/60 backdrop-blur-sm"
      onClick={onClose}
    >
      <div
        className="w-full max-w-[420px] bg-zinc-900 border border-white/10 rounded-xl shadow-2xl"
        onClick={(e) => e.stopPropagation()}
      >
        <div className="flex items-start gap-3 p-5">
          {destructive && (
            <div className="w-9 h-9 rounded-full bg-red-500/10 border border-red-500/20 flex items-center justify-center shrink-0">
              <AlertTriangle className="w-4 h-4 text-red-400" />
            </div>
          )}
          <div className="flex-1 min-w-0">
            <h3 id="confirmation-dialog-title" className="text-base font-semibold text-white">
              {title}
            </h3>
            <p className="text-sm text-zinc-400 mt-1 leading-relaxed">{description}</p>
          </div>
          <button
            onClick={onClose}
            aria-label="Close"
            className="text-zinc-500 hover:text-zinc-200 transition-colors shrink-0"
          >
            <X className="w-4 h-4" />
          </button>
        </div>
        <div className="flex justify-end gap-2 px-5 pb-5">
          <button
            onClick={onClose}
            className="px-3.5 py-2 rounded-md text-sm font-medium text-zinc-300 hover:bg-white/[0.05] transition-colors"
          >
            {cancelLabel}
          </button>
          <button
            onClick={onConfirm}
            className={cn(
              'px-3.5 py-2 rounded-md text-sm font-semibold text-white transition-colors active:scale-[0.97]',
              destructive
                ? 'bg-red-500 hover:bg-red-400'
                : 'bg-indigo-500 hover:bg-indigo-400'
            )}
          >
            {confirmLabel}
          </button>
        </div>
      </div>
    </div>
  );
}