Delete Dialogue

Cinematic confirmation dialogs built with physics-based GSAP animations. Standardizing the "Confirm Delete" experience with high-fidelity micro-interactions.

shadcn/ui Compatible
GSAP Powered

Universal Integration

Each variant manages its own animation timeline. To sync with your backend, set setIsDeleting(true) as your loading trigger.

const handleDelete = async () => {
  setIsDeleting(true); // Triggers cinematic sequence
  
  try {
    await api.delete(id); // Performs actual deletion
  } catch (error) {
    setIsDeleting(false); // Resets state on failure
  }
};
01

The Original Bin

The classic physics-based trash disposal animation. Features a rotating lid, gravitational pull-in, and a bounce-closed completion state.

Installation

npx aftershade-ui add delete-bin

Or use shadcn: npx shadcn@latest add https://aftershade.pages.dev/registry/delete-bin.json

Source Code

"use client";

import React, { useRef, useState, useEffect } from "react";
import {
  AlertDialog,
  AlertDialogAction,
  AlertDialogCancel,
  AlertDialogContent,
  AlertDialogDescription,
  AlertDialogFooter,
  AlertDialogHeader,
  AlertDialogTitle,
  AlertDialogTrigger,
} from "@/components/ui/alert-dialog";
import { Button } from "@/components/ui/button";
import { Trash2, CheckCircle2 } from "lucide-react";
import gsap from "gsap";

export function DeleteBin() {
  const [isOpen, setIsOpen] = useState(false);
  const [isDeleting, setIsDeleting] = useState(false);
  const [isDeleted, setIsDeleted] = useState(false);
  
  const binRef = useRef(null);
  const lidRef = useRef(null);
  const garbageRef = useRef(null);
  const successRef = useRef(null);
  const timelineRef = useRef(null);

  useEffect(() => {
    if (!isDeleting || isDeleted) return;

    const timeoutId = setTimeout(() => {
      if (!binRef.current || !lidRef.current || !garbageRef.current || !successRef.current) return;

      gsap.killTweensOf([binRef.current, lidRef.current, garbageRef.current, successRef.current]);

      const tl = gsap.timeline({
        onComplete: () => {
          setIsDeleting(false);
          setIsDeleted(true);
          setTimeout(() => setIsOpen(false), 1200);
        }
      });
      
      timelineRef.current = tl;

      gsap.set(garbageRef.current, { opacity: 1, y: -40, scale: 1, rotation: 0, x: 0 });
      gsap.set(lidRef.current, { rotation: 0, transformOrigin: "left bottom" });
      gsap.set(successRef.current, { opacity: 0, scale: 0.5 });
      gsap.set(binRef.current, { opacity: 1, scale: 1 });

      tl.to(lidRef.current, { rotate: -45, duration: 0.4, ease: "power2.out" })
      .to(garbageRef.current, { y: 60, x: 5, scale: 0.1, rotation: 180, opacity: 0, duration: 0.7, ease: "power2.in" }, "-=0.1")
      .to(lidRef.current, { rotate: 0, duration: 0.3, ease: "bounce.out" })
      .to(binRef.current, { scale: 1.1, duration: 0.1, yoyo: true, repeat: 1 })
      .to(binRef.current, { opacity: 0, scale: 0.8, duration: 0.3, delay: 0.3 })
      .to(successRef.current, { opacity: 1, scale: 1, duration: 0.4, ease: "back.out(1.7)" });
    }, 50);

    return () => {
      clearTimeout(timeoutId);
      if (timelineRef.current) timelineRef.current.kill();
    };
  }, [isDeleting, isDeleted]);

  return (
    <AlertDialog open={isOpen} onOpenChange={setIsOpen}>
      <AlertDialogTrigger asChild>
        <Button variant="destructive" className="rounded-full px-8 h-12">
          <Trash2 className="mr-2 h-5 w-5" />
          Delete Asset
        </Button>
      </AlertDialogTrigger>
      <AlertDialogContent className="max-w-[400px] rounded-2xl p-8 bg-background">
        {/* Animation Content (Bin) */}
      </AlertDialogContent>
    </AlertDialog>
  );
}
02

Ghost Void

A high-fluidity spectral fade. Uses GSAP Blur and Scale filters to create a hauntingly smooth data disintegration effect.

Installation

npx aftershade-ui add delete-ghost

Or use shadcn: npx shadcn@latest add https://aftershade.pages.dev/registry/delete-ghost.json

Source Code

"use client";

import React, { useRef, useState, useEffect } from "react";
import {
  AlertDialog,
  AlertDialogContent,
  AlertDialogHeader,
  AlertDialogTitle,
  AlertDialogDescription,
  AlertDialogFooter,
  AlertDialogCancel,
  AlertDialogTrigger,
} from "@/components/ui/alert-dialog";
import { Button } from "@/components/ui/button";
import { Ghost, CheckCircle2 } from "lucide-react";
import gsap from "gsap";

export function DeleteGhost() {
  const [isOpen, setIsOpen] = useState(false);
  const [isDeleting, setIsDeleting] = useState(false);
  const [isDeleted, setIsDeleted] = useState(false);
  const fileRef = useRef(null);

  useEffect(() => {
    if (!isDeleting || isDeleted) return;
    const tl = gsap.timeline({ 
      onComplete: () => { 
        setIsDeleting(false); 
        setIsDeleted(true); 
        setTimeout(() => setIsOpen(false), 1200); 
      } 
    });
    
    tl.to(fileRef.current, { y: -20, opacity: 0.5, filter: "blur(10px)", duration: 0.5 })
      .to(fileRef.current, { scale: 2, opacity: 0, duration: 0.5, ease: "power2.in" });
  }, [isDeleting, isDeleted]);

  return (
    <AlertDialog open={isOpen} onOpenChange={setIsOpen}>
      <AlertDialogTrigger asChild>
        <Button variant="outline" className="rounded-full h-12 px-8 group">
          <Ghost className="mr-2 h-5 w-5 group-hover:animate-pulse" />
          Ghost Void
        </Button>
      </AlertDialogTrigger>
      <AlertDialogContent className="max-w-[400px] rounded-2xl p-8 bg-background border-border">
        {/* Animation Content (Ghost) */}
      </AlertDialogContent>
    </AlertDialog>
  );
}
03

Rocket Yeet

A high-energy orbital launch sequence. Features randomized shaking dynamics followed by a high-velocity `power4.in` exit.

Installation

npx aftershade-ui add delete-rocket

Or use shadcn: npx shadcn@latest add https://aftershade.pages.dev/registry/delete-rocket.json

Source Code

"use client";

import React, { useRef, useState, useEffect } from "react";
import {
  AlertDialog,
  AlertDialogContent,
  AlertDialogHeader,
  AlertDialogTitle,
  AlertDialogDescription,
  AlertDialogFooter,
  AlertDialogCancel,
  AlertDialogTrigger,
} from "@/components/ui/alert-dialog";
import { Button } from "@/components/ui/button";
import { Rocket, CheckCircle2 } from "lucide-react";
import gsap from "gsap";

export function DeleteRocket() {
  const [isOpen, setIsOpen] = useState(false);
  const [isDeleting, setIsDeleting] = useState(false);
  const [isDeleted, setIsDeleted] = useState(false);
  const containerRef = useRef(null);

  useEffect(() => {
    if (!isDeleting || isDeleted) return;
    const tl = gsap.timeline({ 
      onComplete: () => { 
        setIsDeleting(false); 
        setIsDeleted(true); 
        setTimeout(() => setIsOpen(false), 1200); 
      } 
    });
    
    tl.to(containerRef.current, { y: 20, duration: 0.2, yoyo: true, repeat: 3 })
      .to(containerRef.current, { y: -600, scale: 0.5, rotate: 15, duration: 0.8, ease: "power4.in" });
  }, [isDeleting, isDeleted]);

  return (
    <AlertDialog open={isOpen} onOpenChange={setIsOpen}>
      <AlertDialogTrigger asChild>
        <Button variant="outline" className="rounded-full h-12 px-8 group">
          <Rocket className="mr-2 h-5 w-5 group-hover:-translate-y-1 group-hover:translate-x-1 transition-transform" />
          Rocket Yeet
        </Button>
      </AlertDialogTrigger>
      <AlertDialogContent className="max-w-[400px] rounded-2xl p-8 bg-background border-border">
        {/* Animation Content (Rocket) */}
      </AlertDialogContent>
    </AlertDialog>
  );
}