Bloom Logo

Alert Dialog

A confirmation dialog modal that interrupts the user with critical content requiring an explicit response, text validation verification, or async operation loading states before proceeding.

importimport { AlertDialog } from "@/components/ui/alertDialog/alertDialog";
$npx @bloomui-react/cli add alertDialog
alertDialog.tsx
"use client";

import * as DialogPrimitive from "@radix-ui/react-dialog";
import * as React from "react";
import { cn } from "@/lib/utils";

const AlertDialog = ({
  modal = true,
  ...props
}: React.ComponentPropsWithoutRef<typeof DialogPrimitive.Root>) => (
  <DialogPrimitive.Root modal={modal} {...props} />
);
AlertDialog.displayName = "AlertDialog";

const AlertDialogTrigger = DialogPrimitive.Trigger;
const AlertDialogPortal = DialogPrimitive.Portal;

const AlertDialogOverlay = React.forwardRef<
  HTMLDivElement,
  React.ComponentPropsWithoutRef<"div">
>(({ className, ...props }, ref) => (
  <DialogPrimitive.Close asChild>
    <div
      ref={ref}
      className={cn(
        "fixed inset-0 z-50 cursor-pointer pointer-events-auto transition-all duration-300 bg-black/60 backdrop-blur-xs data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
        className,
      )}
      {...props}
    />
  </DialogPrimitive.Close>
));
AlertDialogOverlay.displayName = "AlertDialogOverlay";

const AlertDialogContent = React.forwardRef<
  React.ComponentRef<typeof DialogPrimitive.Content>,
  React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
>(({ className, children, ...props }, ref) => (
  <AlertDialogPortal>
    <AlertDialogOverlay />
    <div className="fixed inset-0 z-50 pointer-events-none max-w-[110rem] mx-auto left-0 right-0 overflow-hidden">
      <div className="absolute inset-0 flex items-center justify-center p-4">
        <DialogPrimitive.Content
          ref={ref}
          className={cn(
            "pointer-events-auto relative z-50 grid w-full max-w-lg gap-4 border border-zinc-200/80 dark:border-zinc-800/80 bg-white dark:bg-zinc-900 p-6 shadow-2xl duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 rounded-3xl text-zinc-900 dark:text-zinc-100",
            className,
          )}
          {...props}
        >
          {children}
        </DialogPrimitive.Content>
      </div>
    </div>
  </AlertDialogPortal>
));
AlertDialogContent.displayName = "AlertDialogContent";

const AlertDialogHeader = ({
  className,
  ...props
}: React.HTMLAttributes<HTMLDivElement>) => (
  <div
    className={cn(
      "flex flex-col space-y-2 text-center sm:text-left",
      className,
    )}
    {...props}
  />
);
AlertDialogHeader.displayName = "AlertDialogHeader";

const AlertDialogFooter = ({
  className,
  ...props
}: React.HTMLAttributes<HTMLDivElement>) => (
  <div
    className={cn(
      "flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2 gap-2 mt-4",
      className,
    )}
    {...props}
  />
);
AlertDialogFooter.displayName = "AlertDialogFooter";

const AlertDialogTitle = React.forwardRef<
  React.ComponentRef<typeof DialogPrimitive.Title>,
  React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
>(({ className, ...props }, ref) => (
  <DialogPrimitive.Title
    ref={ref}
    className={cn(
      "text-lg font-bold text-zinc-900 dark:text-zinc-100 tracking-tight flex items-center gap-2",
      className,
    )}
    {...props}
  />
));
AlertDialogTitle.displayName = "AlertDialogTitle";

const AlertDialogDescription = React.forwardRef<
  React.ComponentRef<typeof DialogPrimitive.Description>,
  React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
>(({ className, ...props }, ref) => (
  <DialogPrimitive.Description
    ref={ref}
    className={cn(
      "text-sm text-zinc-600 dark:text-zinc-400 leading-relaxed",
      className,
    )}
    {...props}
  />
));
AlertDialogDescription.displayName = "AlertDialogDescription";

export type AlertDialogActionColor =
  | "default"
  | "info"
  | "success"
  | "warning"
  | "danger";

export interface AlertDialogActionProps
  extends React.ButtonHTMLAttributes<HTMLButtonElement> {
  color?: AlertDialogActionColor;
  isLoading?: boolean;
}

const actionColorMap: Record<AlertDialogActionColor, string> = {
  danger: "bg-danger hover:opacity-90 text-danger-foreground",
  info: "bg-primary hover:opacity-90 text-primary-foreground",
  success: "bg-success hover:opacity-90 text-success-foreground",
  warning: "bg-warning hover:opacity-95 text-warning-foreground",
  default: "bg-default hover:bg-default/80 text-default-foreground",
};

const AlertDialogAction = React.forwardRef<
  HTMLButtonElement,
  AlertDialogActionProps
>(
  (
    {
      className,
      color = "danger",
      isLoading = false,
      children,
      disabled,
      onClick,
      ...props
    },
    ref,
  ) => {
    const button = (
      <button
        ref={ref}
        disabled={disabled || isLoading}
        className={cn(
          "inline-flex h-9 items-center justify-center gap-2 rounded-xl px-4 text-sm font-semibold cursor-pointer transition-all duration-200 shadow-md disabled:opacity-50 disabled:cursor-not-allowed select-none",
          actionColorMap[color],
          className,
        )}
        onClick={onClick}
        {...props}
      >
        {isLoading ? (
          <>
            <span
              role="status"
              className="h-4 w-4 animate-spin rounded-full border-2 border-current border-t-transparent"
              aria-label="Loading"
            />
            <span>Loading...</span>
          </>
        ) : (
          children
        )}
      </button>
    );

    if (isLoading || disabled) return button;

    return <DialogPrimitive.Close asChild>{button}</DialogPrimitive.Close>;
  },
);
AlertDialogAction.displayName = "AlertDialogAction";

const AlertDialogCancel = React.forwardRef<
  HTMLButtonElement,
  React.ButtonHTMLAttributes<HTMLButtonElement>
>(({ className, ...props }, ref) => (
  <DialogPrimitive.Close asChild>
    <button
      ref={ref}
      className={cn(
        "inline-flex h-9 items-center justify-center rounded-xl border border-zinc-200 dark:border-zinc-800 bg-white dark:bg-zinc-900 px-4 text-sm font-semibold text-zinc-700 dark:text-zinc-300 hover:bg-zinc-100 dark:hover:bg-zinc-800 cursor-pointer transition-all duration-200 mt-2 sm:mt-0 select-none",
        className,
      )}
      {...props}
    />
  </DialogPrimitive.Close>
));
AlertDialogCancel.displayName = "AlertDialogCancel";

export {
  AlertDialog,
  AlertDialogAction,
  AlertDialogCancel,
  AlertDialogContent,
  AlertDialogDescription,
  AlertDialogFooter,
  AlertDialogHeader,
  AlertDialogOverlay,
  AlertDialogPortal,
  AlertDialogTitle,
  AlertDialogTrigger,
};

Default

A standard confirmation dialog modal that interrupts the user with critical content requiring an explicit response before proceeding.

Colors

Configure the primary action button to inherit different semantic colors: default, info, success, warning, and danger.

color: info

Text Validation Confirmation

Require the user to type an exact confirmation string (e.g. DELETE) to unlock the action button.

asChild: anycolor: anydanger: anyto: anyconfirm: anyconfirmInput: anye: anydisabled: any

Custom Header Layout

Embed graphic icons, colored header text, and specialized callouts to emphasize danger or urgency.

asChild: anycolor: anydanger: anyvariant: anybordered: anytext-red-600: anyflex: anyitems-center: anygap-2: anysize-5: any

Async Action Loading

Render a loading spinner state on the action button during asynchronous operations. The dialog programmatically closes upon success.

isLoading: boolean

Props — AlertDialogAction

Properties for customizing the confirmation action button.

PropTypeDefaultDescription
color'default' | 'info' | 'success' | 'warning' | 'danger''danger'Semantic color theme for the confirmation action button.
isLoadingbooleanfalseDisplays a loading spinner indicator and disables interaction.