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"
2
3import { cn } from "@/lib/utils"
4import { FileIcon, Trash2, Upload } from "lucide-react"
5import * as React from "react"
6import { Button } from "./button"
7
8interface FileInputProps {
9 className?: string
10 value?: File | null
11 onChange?: (file: File | null) => void
12 disabled?: boolean
13 accept?: string
14}
15
16const 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)
25
26 const handleFileSelection = (file: File | null) => {
27 if (!disabled) onChange?.(file)
28 }
29
30 const handleDragEvents = (
31 e: React.DragEvent<HTMLDivElement>,
32 isOver: boolean,
33 ) => {
34 e.preventDefault()
35 if (!disabled) setIsDragging(isOver)
36 }
37
38 const handleDrop = (e: React.DragEvent<HTMLDivElement>) => {
39 e.preventDefault()
40 setIsDragging(false)
41
42 if (!disabled) {
43 const file = e.dataTransfer.files?.[0] ?? null
44 handleFileSelection(file)
45 }
46 }
47
48 const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
49 handleFileSelection(e.target.files?.[0] ?? null)
50 }
51
52 const handleRemove = (e: React.MouseEvent<HTMLButtonElement>) => {
53 e.preventDefault()
54 handleFileSelection(null)
55 if (inputRef.current) inputRef.current.value = ""
56 }
57
58 return (
59 <div className={cn("space-y-2", className)}>
60 <div
61 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 <input
75 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 drop
87 </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)} MB
99 </p>
100 </div>
101 <Button
102 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}
117
118export { FileInput }

Form

Click to upload or drag and drop

Upload your file here (Images or PDF).