Not affiliated with official shadcn/ui. Visit ui.shadcn.com for official docs.
Unlock this block—Get Pro at 60% offReact Dialog Block Gift Card
Gift card dialog with preset amounts, custom value input, recipient details, personal message, and delivery options
Looking to implement shadcn/ui blocks?
Join our Discord community for help from other developers.
Give the perfect gift instantly. This React gift card dialog enables users to purchase and send digital gift cards with preset amount selection, custom value input, recipient email and name fields, personalized message textarea, and delivery scheduling options. Built with shadcn/ui Dialog, Input, Textarea, RadioGroup, Button, and Calendar components using Tailwind CSS, shoppers create thoughtful gifts with flexible denominations and delivery timing. Amount presets, custom values, scheduled delivery—perfect for ecommerce stores, restaurant apps, subscription services, or any Next.js application where gift card sales drive revenue and customer acquisition.
"use client";import { useState } from "react";import { Gift, Mail, User, Calendar as CalendarIcon, Check, Sparkles,} from "lucide-react";import { Button } from "@/components/ui/button";import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger,} from "@/components/ui/dialog";import { Input } from "@/components/ui/input";import { Label } from "@/components/ui/label";import { Textarea } from "@/components/ui/textarea";import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";import { Calendar } from "@/components/ui/calendar";import { Popover, PopoverContent, PopoverTrigger,} from "@/components/ui/popover";import { Separator } from "@/components/ui/separator";import { cn } from "@/lib/utils";const presetAmounts = [25, 50, 75, 100, 150, 200];type DeliveryOption = "now" | "scheduled";export const title = "React Dialog Block Gift Card";export default function DialogGiftCard() { const [open, setOpen] = useState(false); const [saved, setSaved] = useState(false); const [selectedAmount, setSelectedAmount] = useState<number | null>(50); const [customAmount, setCustomAmount] = useState(""); const [recipientName, setRecipientName] = useState(""); const [recipientEmail, setRecipientEmail] = useState(""); const [message, setMessage] = useState(""); const [deliveryOption, setDeliveryOption] = useState<DeliveryOption>("now"); const [scheduledDate, setScheduledDate] = useState<Date | undefined>(); const [showCalendar, setShowCalendar] = useState(false); const finalAmount = customAmount ? parseFloat(customAmount) : selectedAmount; const handleAmountSelect = (amount: number) => { setSelectedAmount(amount); setCustomAmount(""); }; const handleCustomAmountChange = (value: string) => { setCustomAmount(value); if (value) { setSelectedAmount(null); } }; const handleSave = () => { console.log({ amount: finalAmount, recipientName, recipientEmail, message, deliveryOption, scheduledDate, }); setSaved(true); }; const handleClose = () => { setOpen(false); setTimeout(() => { setSaved(false); setSelectedAmount(50); setCustomAmount(""); setRecipientName(""); setRecipientEmail(""); setMessage(""); setDeliveryOption("now"); setScheduledDate(undefined); setShowCalendar(false); }, 200); }; const formatDate = (date: Date) => { return date.toLocaleDateString("en-US", { weekday: "short", month: "short", day: "numeric", }); }; const isValidEmail = recipientEmail.includes("@") && recipientEmail.includes("."); const isValidAmount = finalAmount && finalAmount >= 10 && finalAmount <= 500; const isValid = isValidAmount && recipientName.trim() && isValidEmail && (deliveryOption === "now" || scheduledDate); return ( <Dialog open={open} onOpenChange={setOpen}> <div className="flex min-h-[350px] items-center justify-center"> <DialogTrigger asChild> <Button variant="outline"> <Gift className="mr-2 h-4 w-4" /> Send Gift Card </Button> </DialogTrigger> </div> <DialogContent className="sm:max-w-md"> {saved ? ( <> <DialogHeader className="text-center"> <div className="mx-auto flex h-12 w-12 items-center justify-center rounded-full bg-primary/10 mb-4"> <Check className="h-6 w-6 text-primary" /> </div> <DialogTitle>Gift Card Sent!</DialogTitle> <DialogDescription> A ${finalAmount} gift card {deliveryOption === "now" ? "has been sent" : `will be sent on ${scheduledDate && formatDate(scheduledDate)}`} to {recipientEmail}. </DialogDescription> </DialogHeader> <div className="flex justify-center py-4"> <div className="rounded-lg border bg-gradient-to-br from-primary/5 to-primary/10 p-6 text-center"> <Sparkles className="h-8 w-8 mx-auto mb-2 text-primary" /> <p className="text-2xl font-bold">${finalAmount}</p> <p className="text-sm text-muted-foreground">Gift Card</p> </div> </div> <DialogFooter> <Button onClick={handleClose} className="w-full"> Done </Button> </DialogFooter> </> ) : ( <> <DialogHeader> <DialogTitle className="flex items-center gap-2"> <Gift className="h-5 w-5" /> Send a Gift Card </DialogTitle> <DialogDescription> Choose an amount and personalize your gift. </DialogDescription> </DialogHeader> <div className="space-y-4 py-4"> <div className="space-y-3"> <Label>Select amount</Label> <div className="grid grid-cols-3 gap-2"> {presetAmounts.map((amount) => ( <button key={amount} onClick={() => handleAmountSelect(amount)} className={cn( "rounded-lg border p-3 text-center transition-colors", selectedAmount === amount ? "border-primary bg-primary/5 text-primary font-semibold" : "hover:border-muted-foreground/50" )} > ${amount} </button> ))} </div> <div className="flex items-center gap-2"> <span className="text-sm text-muted-foreground">or</span> <div className="relative flex-1"> <span className="absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground"> $ </span> <Input type="number" placeholder="Custom amount" value={customAmount} onChange={(e) => handleCustomAmountChange(e.target.value)} className="pl-7" min="10" max="500" /> </div> </div> <p className="text-xs text-muted-foreground"> Min $10 • Max $500 </p> </div> <Separator /> <div className="space-y-3"> <Label>Recipient details</Label> <div className="space-y-2"> <div className="relative"> <User className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" /> <Input placeholder="Recipient's name" value={recipientName} onChange={(e) => setRecipientName(e.target.value)} className="pl-9" /> </div> <div className="relative"> <Mail className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" /> <Input type="email" placeholder="Recipient's email" value={recipientEmail} onChange={(e) => setRecipientEmail(e.target.value)} className="pl-9" /> </div> </div> </div> <div className="space-y-2"> <Label htmlFor="message">Personal message (optional)</Label> <Textarea id="message" placeholder="Add a personal note..." value={message} onChange={(e) => setMessage(e.target.value)} className="h-20 resize-none" maxLength={200} /> <p className="text-xs text-muted-foreground text-right"> {message.length}/200 </p> </div> <div className="space-y-3"> <Label>Delivery</Label> <RadioGroup value={deliveryOption} onValueChange={(value) => setDeliveryOption(value as DeliveryOption)} className="grid grid-cols-2 gap-3" > <Label htmlFor="now" className={cn( "flex items-center gap-2 rounded-lg border p-3 cursor-pointer transition-colors", deliveryOption === "now" && "border-primary bg-primary/5" )} > <RadioGroupItem value="now" id="now" /> <span className="text-sm font-medium">Send now</span> </Label> <Label htmlFor="scheduled" className={cn( "flex items-center gap-2 rounded-lg border p-3 cursor-pointer transition-colors", deliveryOption === "scheduled" && "border-primary bg-primary/5" )} > <RadioGroupItem value="scheduled" id="scheduled" /> <span className="text-sm font-medium">Schedule</span> </Label> </RadioGroup> {deliveryOption === "scheduled" && ( <Popover open={showCalendar} onOpenChange={setShowCalendar}> <PopoverTrigger asChild> <Button variant="outline" className="w-full justify-start text-left font-normal" > <CalendarIcon className="mr-2 h-4 w-4" /> {scheduledDate ? formatDate(scheduledDate) : "Pick a date"} </Button> </PopoverTrigger> <PopoverContent className="w-auto p-0" align="start"> <Calendar mode="single" selected={scheduledDate} onSelect={(date) => { setScheduledDate(date); setShowCalendar(false); }} disabled={(date) => date < new Date()} initialFocus /> </PopoverContent> </Popover> )} </div> </div> <DialogFooter className="gap-2 sm:gap-0"> <Button variant="outline" onClick={handleClose}> Cancel </Button> <Button onClick={handleSave} disabled={!isValid}> Purchase ${finalAmount || 0} </Button> </DialogFooter> </> )} </DialogContent> </Dialog> );}Installation
npx shadcn@latest add https://www.shadcn.io/registry/dialog-gift-card.jsonnpx shadcn@latest add https://www.shadcn.io/registry/dialog-gift-card.jsonpnpm dlx shadcn@latest add https://www.shadcn.io/registry/dialog-gift-card.jsonbunx shadcn@latest add https://www.shadcn.io/registry/dialog-gift-card.jsonRelated blocks you will also like
React Dialog Block Payment Method
Payment card input
React Dialog Block Billing Address
Checkout address form
React Dialog Block Schedule Meeting
Date scheduling
React Dialog Block Success Confirmation
Purchase confirmation
React Dialog Block Share Link
Sharing options
React Dialog Block Quick Note
Message composition