- Alert
- Avatar
- Badge
- Breadcrumb
- Button
- Button Group
- Calendar
- Card
- Carousel
- Chart
- Checkbox
- Checkbox Group
- ComboBox
- Command
- Data Table
- Date Field
- Date Picker
- Date Range Picker
- Dialog
- Disclosure
- Disclosure Group
- Drawer
- Empty
- Field
- File Trigger
- Form
- Grid List
- Hover Card
- Input
- Input Group
- Input OTP
- Item
- Kbd
- Label
- ListBox
- Menu
- Number Field
- Pagination
- Popover
- Progress Bar
- Radio Group
- Range Calendar
- Resizable
- Search Field
- Select
- Separator
- Sheet
- Sidebar
- Skeleton
- Slider
- Sonner
- Spinner
- Switch
- Table
- Tabs
- Tag Group
- Text Field
- Textarea
- Time Field
- Toast
- Toggle Button
- Toggle Button Group
- Tooltip
- Tree
- Typography
A file trigger allows a user to access the file system with any pressable component.
"use client"
import { useState } from "react"
import { Button } from "@/components/ui/button"
import { FileTrigger } from "@/components/ui/file-trigger"
export function FileTriggerDemo() {
const [file, setFile] = useState<string | null>(null)
return (
<div className="flex flex-col gap-4">
<FileTrigger
onSelect={(e) => {
const files = e ? Array.from(e) : []
const filenames = files.map((file) => file.name)
setFile(filenames[0] || null)
}}
>
<Button variant="outline">Select a file</Button>
</FileTrigger>
{file && (
<p className="text-muted-foreground text-sm">Selected: {file}</p>
)}
</div>
)
}
Installation
pnpmnpmyarnbunpnpm dlx shadcn@latest add file-trigger
Usage
import { FileTrigger } from "@/components/ui/file-trigger"
import { Button } from "@/components/ui/button"<FileTrigger onSelect={(files) => console.log(files)}>
<Button>Select a file</Button>
</FileTrigger>Features
A file input can be created with an <input type="file"> element, but this supports limited styling options and may not integrate well with the overall design of a website or application. FileTrigger extends the functionality of the standard file input element by working with a pressable child such as a Button to create accessible file inputs that can be styled as needed.
- Customizable – Works with any pressable React Aria component, and custom components built with
usePress. - File type filtering – Accepts specific file types using MIME types.
- Multiple files – Supports selecting multiple files at once.
- Directory selection – Allows users to select entire directories.
- Media capture – Supports opening device camera on mobile devices.
Examples
Default
"use client"
import { useState } from "react"
import { Button } from "@/components/ui/button"
import { FileTrigger } from "@/components/ui/file-trigger"
export function FileTriggerDemo() {
const [file, setFile] = useState<string | null>(null)
return (
<div className="flex flex-col gap-4">
<FileTrigger
onSelect={(e) => {
const files = e ? Array.from(e) : []
const filenames = files.map((file) => file.name)
setFile(filenames[0] || null)
}}
>
<Button variant="outline">Select a file</Button>
</FileTrigger>
{file && (
<p className="text-muted-foreground text-sm">Selected: {file}</p>
)}
</div>
)
}
"use client"
import { useState } from "react"
import { Button } from "@/components/ui/button"
import { FileTrigger } from "@/components/ui/file-trigger"
export default function FileTriggerDemo() {
const [file, setFile] = useState<string | null>(null)
return (
<>
<FileTrigger
onSelect={(e) => {
const files = e ? Array.from(e) : []
const filenames = files.map((file) => file.name)
setFile(filenames[0] || null)
}}
>
<Button variant="outline">Select a file</Button>
</FileTrigger>
{file && <p>Selected: {file}</p>}
</>
)
}Multiple Files
"use client"
import { useState } from "react"
import { Button } from "@/components/ui/button"
import { FileTrigger } from "@/components/ui/file-trigger"
export function FileTriggerMultiple() {
const [files, setFiles] = useState<string[]>([])
return (
<div className="flex flex-col gap-4">
<FileTrigger
allowsMultiple
onSelect={(e) => {
const fileList = e ? Array.from(e) : []
const filenames = fileList.map((file) => file.name)
setFiles(filenames)
}}
>
<Button variant="outline">Select multiple files</Button>
</FileTrigger>
{files.length > 0 && (
<div className="text-muted-foreground text-sm">
<p className="font-medium">Selected files ({files.length}):</p>
<ul className="mt-2 space-y-1">
{files.map((file, index) => (
<li key={index} className="truncate">
{file}
</li>
))}
</ul>
</div>
)}
</div>
)
}
<FileTrigger
allowsMultiple
onSelect={(e) => {
const files = e ? Array.from(e) : []
console.log(files.map((file) => file.name))
}}
>
<Button variant="outline">Select multiple files</Button>
</FileTrigger>Accepted File Types
"use client"
import { useState } from "react"
import { ImageIcon } from "lucide-react"
import { Button } from "@/components/ui/button"
import { FileTrigger } from "@/components/ui/file-trigger"
export function FileTriggerImage() {
const [image, setImage] = useState<string | null>(null)
return (
<div className="flex flex-col gap-4">
<FileTrigger
acceptedFileTypes={[
"image/png",
"image/jpeg",
"image/jpg",
"image/gif",
]}
onSelect={(e) => {
const files = e ? Array.from(e) : []
if (files.length > 0) {
setImage(files[0].name)
}
}}
>
<Button variant="outline">
<ImageIcon />
Select an image
</Button>
</FileTrigger>
{image && (
<p className="text-muted-foreground text-sm">Selected: {image}</p>
)}
</div>
)
}
<FileTrigger
acceptedFileTypes={["image/png", "image/jpeg", "image/jpg", "image/gif"]}
onSelect={(e) => {
const files = e ? Array.from(e) : []
console.log(files[0])
}}
>
<Button variant="outline">
<ImageIcon />
Select an image
</Button>
</FileTrigger>You can specify file types using MIME types. Common examples include:
image/*- All image typesimage/png,image/jpeg- Specific image formatsvideo/*- All video typesapplication/pdf- PDF files.txt,.doc- File extensions
Directory Selection
"use client"
import { useState } from "react"
import { FolderIcon } from "lucide-react"
import { Button } from "@/components/ui/button"
import { FileTrigger } from "@/components/ui/file-trigger"
export function FileTriggerDirectory() {
const [files, setFiles] = useState<string[]>([])
return (
<div className="flex flex-col gap-4">
<FileTrigger
acceptDirectory
onSelect={(e) => {
if (e) {
const fileList = Array.from(e).map((file) =>
file.webkitRelativePath !== ""
? file.webkitRelativePath
: file.name
)
setFiles(fileList)
}
}}
>
<Button variant="outline">
<FolderIcon />
Select a directory
</Button>
</FileTrigger>
{files.length > 0 && (
<div className="text-muted-foreground text-sm">
<p className="font-medium">
Directory contents ({files.length} files):
</p>
<ul className="mt-2 max-h-32 space-y-1 overflow-y-auto">
{files.slice(0, 10).map((file, index) => (
<li key={index} className="truncate text-xs">
{file}
</li>
))}
{files.length > 10 && (
<li className="text-xs italic">
...and {files.length - 10} more files
</li>
)}
</ul>
</div>
)}
</div>
)
}
<FileTrigger
acceptDirectory
onSelect={(e) => {
if (e) {
const fileList = Array.from(e).map((file) =>
file.webkitRelativePath !== ""
? file.webkitRelativePath
: file.name
)
console.log(fileList)
}
}}
>
<Button variant="outline">
<FolderIcon />
Select a directory
</Button>
</FileTrigger>This reflects the webkitdirectory HTML attribute and allows users to select directories and their contents. Please note that support for this feature varies from browser to browser.
Media Capture
"use client"
import { useState } from "react"
import { CameraIcon } from "lucide-react"
import { Button } from "@/components/ui/button"
import { FileTrigger } from "@/components/ui/file-trigger"
export function FileTriggerCamera() {
const [image, setImage] = useState<string | null>(null)
return (
<div className="flex flex-col gap-4">
<FileTrigger
defaultCamera="environment"
acceptedFileTypes={["image/*"]}
onSelect={(e) => {
const files = e ? Array.from(e) : []
if (files.length > 0) {
setImage(files[0].name)
}
}}
>
<Button variant="outline">
<CameraIcon />
Open Camera
</Button>
</FileTrigger>
{image && (
<p className="text-muted-foreground text-sm">Captured: {image}</p>
)}
</div>
)
}
<FileTrigger
defaultCamera="environment"
acceptedFileTypes={["image/*"]}
onSelect={(e) => {
const files = e ? Array.from(e) : []
console.log(files[0])
}}
>
<Button variant="outline">
<CameraIcon />
Open Camera
</Button>
</FileTrigger>To specify the media capture mechanism to capture media on the spot, pass user for the user-facing camera or environment for the outward-facing camera via the defaultCamera prop.
This behavior only works on mobile devices. On desktop devices, it will open the file system like normal.
API Reference
FileTrigger
The FileTrigger component wraps around a pressable child and includes a visually hidden input element that allows the user to select files from their device.
| Prop | Type | Default | Description |
|---|---|---|---|
acceptedFileTypes | string[] | - | Specifies what MIME types of files are allowed. |
allowsMultiple | boolean | false | Whether multiple files can be selected. |
defaultCamera | "user" | "environment" | - | Specifies the use of a media capture mechanism to capture the media on the spot. |
acceptDirectory | boolean | false | Enables the selection of directories instead of individual files. |
onSelect | (files: FileList | null) => void | - | Handler called when a user selects files. Receives a FileList object or null if cancelled. |
If a visual label is not provided on the pressable child, then an aria-label or aria-labelledby prop must be passed to identify the file trigger to assistive technology.
Apart from the props above, the FileTrigger component also supports the props from the react-aria-components FileTrigger component. Check the React Aria documentation for more details.