File Input
It supports drag-and-drop functionality, displays a preview of selected files, and ensures a smooth experience with clear feedback. Designed for seamless integration into multi-step forms.
Click to upload or drag and drop
Installation
Create the component inside the /components/ui/ folder to keep your project structure organized.
1"use client"23import { cn } from "@/lib/utils"4import { FileIcon, Trash2, Upload } from "lucide-react"5import * as React from "react"6import { Button } from "./button"78interface FileInputProps {9 className?: string10 value?: File | null11 onChange?: (file: File | null) => void12 disabled?: boolean13 accept?: string14}1516const FileInput = ({17 className,18 value,19 onChange,20 disabled,21 accept,22}: FileInputProps) => {23 const [isDragging, setIsDragging] = React.useState(false)24 const inputRef = React.useRef<HTMLInputElement>(null)2526 const handleFileSelection = (file: File | null) => {27 if (!disabled) onChange?.(file)28 }2930 const handleDragEvents = (31 e: React.DragEvent<HTMLDivElement>,32 isOver: boolean,33 ) => {34 e.preventDefault()35 if (!disabled) setIsDragging(isOver)36 }3738 const handleDrop = (e: React.DragEvent<HTMLDivElement>) => {39 e.preventDefault()40 setIsDragging(false)4142 if (!disabled) {43 const file = e.dataTransfer.files?.[0] ?? null44 handleFileSelection(file)45 }46 }4748 const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {49 handleFileSelection(e.target.files?.[0] ?? null)50 }5152 const handleRemove = (e: React.MouseEvent<HTMLButtonElement>) => {53 e.preventDefault()54 handleFileSelection(null)55 if (inputRef.current) inputRef.current.value = ""56 }5758 return (59 <div className={cn("space-y-2", className)}>60 <div61 onClick={() => inputRef.current?.click()}62 onDragOver={(e) => handleDragEvents(e, true)}63 onDragLeave={(e) => handleDragEvents(e, false)}64 onDrop={handleDrop}65 className={cn(66 "relative cursor-pointer rounded-md border border-dashed border-muted-foreground/25 px-6 py-8 text-center transition-colors hover:bg-muted/50",67 isDragging && "border-muted-foreground/50 bg-muted/50",68 disabled && "cursor-not-allowed opacity-60",69 )}70 role="button"71 tabIndex={0}72 aria-disabled={disabled}73 >74 <input75 ref={inputRef}76 type="file"77 accept={accept}78 disabled={disabled}79 onChange={handleChange}80 className="hidden"81 />82 <div className="flex flex-col items-center gap-1">83 <Upload className="h-8 w-8 text-muted-foreground" />84 <p className="text-sm text-muted-foreground">85 <span className="font-semibold text-primary">Click to upload</span>{" "}86 or drag and drop87 </p>88 </div>89 </div>90 {value && (91 <div className="flex items-center gap-2 rounded-md bg-muted/50 p-2">92 <div className="rounded-md bg-background p-2">93 <FileIcon className="size-4 text-muted-foreground" />94 </div>95 <div className="flex-1 min-w-0">96 <p className="truncate text-sm font-medium">{value.name}</p>97 <p className="text-xs text-muted-foreground">98 {(value.size / 1024 / 1024).toFixed(2)} MB99 </p>100 </div>101 <Button102 type="button"103 variant="ghost"104 size="icon"105 disabled={disabled}106 onClick={handleRemove}107 className="flex-none size-8"108 >109 <Trash2 className="size-4 text-muted-foreground hover:text-destructive" />110 <span className="sr-only">Remove file</span>111 </Button>112 </div>113 )}114 </div>115 )116}117118export { FileInput }