Not affiliated with official shadcn/ui. Visit ui.shadcn.com for official docs.
Unlock this block—Get Pro at 60% offReact Dialog Block Data Import Wizard
Data import wizard dialog with file upload, column mapping, validation preview, and import progress tracking
Looking to implement shadcn/ui blocks?
Join our Discord community for help from other developers.
Import data without the headaches. This React data import wizard dialog guides users through file upload, column mapping, validation, and progress tracking in a clear multi-step flow. Built with shadcn/ui Dialog, Button, Select, Progress, Table components and Lucide React Upload icon, users import CSV and Excel files with confidence through visual mapping and error previews. Drag-drop upload, smart column detection, validation warnings—perfect for CRM platforms, analytics tools, or any data-driven app where bulk imports need to be foolproof.
"use client";import { useState } from "react";import { Upload, FileSpreadsheet, ArrowRight, Check, AlertCircle, X, Download,} from "lucide-react";import { Button } from "@/components/ui/button";import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger,} from "@/components/ui/dialog";import { Label } from "@/components/ui/label";import { Progress } from "@/components/ui/progress";import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue,} from "@/components/ui/select";import { Separator } from "@/components/ui/separator";import { Badge } from "@/components/ui/badge";import { cn } from "@/lib/utils";type Step = "upload" | "mapping" | "preview" | "importing" | "complete";const systemFields = [ { id: "name", label: "Full Name", required: true }, { id: "email", label: "Email Address", required: true }, { id: "phone", label: "Phone Number", required: false }, { id: "company", label: "Company", required: false }, { id: "role", label: "Job Title", required: false },];const sampleColumns = ["Name", "Email", "Phone", "Company Name", "Position"];const sampleData = [ { name: "John Smith", email: "[email protected]", phone: "+1 555-0101", company: "Acme Inc", role: "Manager" }, { name: "Jane Doe", email: "[email protected]", phone: "+1 555-0102", company: "Tech Corp", role: "Developer" }, { name: "Bob Wilson", email: "invalid-email", phone: "+1 555-0103", company: "StartupXYZ", role: "Designer" },];export const title = "React Dialog Block Data Import Wizard";export default function DialogDataImport() { const [open, setOpen] = useState(false); const [step, setStep] = useState<Step>("upload"); const [file, setFile] = useState<File | null>(null); const [mappings, setMappings] = useState<Record<string, string>>({ name: "Name", email: "Email", phone: "Phone", company: "Company Name", role: "Position", }); const [progress, setProgress] = useState(0); const [dragActive, setDragActive] = useState(false); const handleDrag = (e: React.DragEvent) => { e.preventDefault(); e.stopPropagation(); if (e.type === "dragenter" || e.type === "dragover") { setDragActive(true); } else if (e.type === "dragleave") { setDragActive(false); } }; const handleDrop = (e: React.DragEvent) => { e.preventDefault(); e.stopPropagation(); setDragActive(false); if (e.dataTransfer.files && e.dataTransfer.files[0]) { setFile(e.dataTransfer.files[0]); } }; const handleFileInput = (e: React.ChangeEvent<HTMLInputElement>) => { if (e.target.files && e.target.files[0]) { setFile(e.target.files[0]); } }; const handleMapping = (fieldId: string, column: string) => { setMappings((prev) => ({ ...prev, [fieldId]: column })); }; const startImport = () => { setStep("importing"); setProgress(0); const interval = setInterval(() => { setProgress((prev) => { if (prev >= 100) { clearInterval(interval); setStep("complete"); return 100; } return prev + 10; }); }, 300); }; const handleClose = () => { setOpen(false); setTimeout(() => { setStep("upload"); setFile(null); setProgress(0); }, 200); }; return ( <Dialog open={open} onOpenChange={setOpen}> <div className="flex min-h-[350px] items-center justify-center"> <DialogTrigger asChild> <Button variant="outline"> <Upload className="mr-2 h-4 w-4" /> Import Data </Button> </DialogTrigger> </div> <DialogContent className="sm:max-w-lg"> {step === "upload" && ( <> <DialogHeader> <DialogTitle>Import Data</DialogTitle> <DialogDescription> Upload a CSV or Excel file to import your data. </DialogDescription> </DialogHeader> <div className="py-4"> <div className={cn( "border-2 border-dashed rounded-lg p-8 text-center transition-colors", dragActive ? "border-primary bg-primary/5" : "border-muted-foreground/25", file && "border-green-500 bg-green-50" )} onDragEnter={handleDrag} onDragLeave={handleDrag} onDragOver={handleDrag} onDrop={handleDrop} > {file ? ( <div className="space-y-2"> <FileSpreadsheet className="h-10 w-10 mx-auto text-green-600" /> <p className="font-medium">{file.name}</p> <p className="text-sm text-muted-foreground"> {(file.size / 1024).toFixed(1)} KB </p> <Button variant="ghost" size="sm" onClick={() => setFile(null)} > <X className="mr-2 h-4 w-4" /> Remove </Button> </div> ) : ( <div className="space-y-4"> <Upload className="h-10 w-10 mx-auto text-muted-foreground" /> <div> <p className="font-medium"> Drag and drop your file here </p> <p className="text-sm text-muted-foreground"> or click to browse </p> </div> <input type="file" accept=".csv,.xlsx,.xls" onChange={handleFileInput} className="hidden" id="file-upload" /> <div className="flex justify-center"> <Label htmlFor="file-upload" className="cursor-pointer"> <Button variant="outline" asChild> <span>Choose File</span> </Button> </Label> </div> <p className="text-xs text-muted-foreground"> Supports CSV and Excel files up to 10MB </p> </div> )} </div> </div> <DialogFooter> <Button variant="outline" onClick={handleClose}> Cancel </Button> <Button onClick={() => setStep("mapping")} disabled={!file}> Continue <ArrowRight className="ml-2 h-4 w-4" /> </Button> </DialogFooter> </> )} {step === "mapping" && ( <> <DialogHeader> <DialogTitle>Map Columns</DialogTitle> <DialogDescription> Match your file columns to the system fields. </DialogDescription> </DialogHeader> <div className="py-4 space-y-4"> {systemFields.map((field) => ( <div key={field.id} className="flex items-center justify-between gap-4" > <div className="flex items-center gap-2 min-w-[140px]"> <Label className="text-sm">{field.label}</Label> {field.required && ( <Badge variant="secondary" className="text-xs"> Required </Badge> )} </div> <Select value={mappings[field.id]} onValueChange={(value) => handleMapping(field.id, value)} > <SelectTrigger className="w-[200px]"> <SelectValue placeholder="Select column" /> </SelectTrigger> <SelectContent> <SelectItem value="skip">-- Skip --</SelectItem> {sampleColumns.map((col) => ( <SelectItem key={col} value={col}> {col} </SelectItem> ))} </SelectContent> </Select> </div> ))} </div> <DialogFooter> <Button variant="outline" onClick={() => setStep("upload")}> Back </Button> <Button onClick={() => setStep("preview")}> Preview Import <ArrowRight className="ml-2 h-4 w-4" /> </Button> </DialogFooter> </> )} {step === "preview" && ( <> <DialogHeader> <DialogTitle>Preview & Validate</DialogTitle> <DialogDescription> Review the data before importing. </DialogDescription> </DialogHeader> <div className="py-4 space-y-4"> <div className="flex items-center justify-between text-sm"> <div className="flex items-center gap-2"> <Check className="h-4 w-4 text-green-600" /> <span>2 valid records</span> </div> <div className="flex items-center gap-2"> <AlertCircle className="h-4 w-4 text-amber-500" /> <span>1 record with errors</span> </div> </div> <div className="rounded-lg border overflow-hidden"> <div className="overflow-x-auto"> <table className="w-full text-sm"> <thead className="bg-muted"> <tr> <th className="px-3 py-2 text-left font-medium">Status</th> <th className="px-3 py-2 text-left font-medium">Name</th> <th className="px-3 py-2 text-left font-medium">Email</th> <th className="px-3 py-2 text-left font-medium">Company</th> </tr> </thead> <tbody> {sampleData.map((row, i) => ( <tr key={i} className="border-t"> <td className="px-3 py-2"> {row.email.includes("@") ? ( <Check className="h-4 w-4 text-green-600" /> ) : ( <AlertCircle className="h-4 w-4 text-amber-500" /> )} </td> <td className="px-3 py-2">{row.name}</td> <td className={cn( "px-3 py-2", !row.email.includes("@") && "text-amber-600" )}> {row.email} {!row.email.includes("@") && ( <span className="block text-xs">Invalid email format</span> )} </td> <td className="px-3 py-2">{row.company}</td> </tr> ))} </tbody> </table> </div> </div> <p className="text-xs text-muted-foreground"> Records with errors will be skipped. You can download an error report after import. </p> </div> <DialogFooter> <Button variant="outline" onClick={() => setStep("mapping")}> Back </Button> <Button onClick={startImport}> Import 2 Records <ArrowRight className="ml-2 h-4 w-4" /> </Button> </DialogFooter> </> )} {step === "importing" && ( <> <DialogHeader> <DialogTitle>Importing...</DialogTitle> <DialogDescription> Please wait while we import your data. </DialogDescription> </DialogHeader> <div className="py-8 space-y-4"> <Progress value={progress} className="h-2" /> <p className="text-center text-sm text-muted-foreground"> {progress < 100 ? `Processing records... ${progress}%` : "Finalizing import..."} </p> </div> </> )} {step === "complete" && ( <> <DialogHeader className="text-center"> <div className="mx-auto flex h-12 w-12 items-center justify-center rounded-full bg-green-100 mb-4"> <Check className="h-6 w-6 text-green-600" /> </div> <DialogTitle>Import Complete</DialogTitle> <DialogDescription className="space-y-1"> <p>Successfully imported 2 records.</p> <p className="text-amber-600">1 record skipped due to errors.</p> </DialogDescription> </DialogHeader> <Separator /> <div className="py-4 space-y-3"> <div className="flex items-center justify-between text-sm"> <span className="text-muted-foreground">Total processed</span> <span className="font-medium">3 records</span> </div> <div className="flex items-center justify-between text-sm"> <span className="text-muted-foreground">Successfully imported</span> <span className="font-medium text-green-600">2 records</span> </div> <div className="flex items-center justify-between text-sm"> <span className="text-muted-foreground">Skipped with errors</span> <span className="font-medium text-amber-600">1 record</span> </div> </div> <DialogFooter className="flex-col sm:flex-row gap-2"> <Button variant="outline" className="sm:flex-1"> <Download className="mr-2 h-4 w-4" /> Download Error Report </Button> <Button onClick={handleClose} className="sm:flex-1"> Done </Button> </DialogFooter> </> )} </DialogContent> </Dialog> );}Installation
npx shadcn@latest add https://www.shadcn.io/registry/dialog-data-import.jsonnpx shadcn@latest add https://www.shadcn.io/registry/dialog-data-import.jsonpnpm dlx shadcn@latest add https://www.shadcn.io/registry/dialog-data-import.jsonbunx shadcn@latest add https://www.shadcn.io/registry/dialog-data-import.jsonRelated blocks you will also like
React Dialog Block Export Data
Data export configuration
React Dialog Block Project Initializer
Multi-step configuration
React Dialog Block Create Workspace
Creation form with options
React Dialog Block Success Confirmation
Success feedback dialog
React Dialog Block File Conflict Resolution
File handling decisions
React Dialog Block Destructive Warning
Warning before action