File Upload
A component that is used to upload multiple files.
Anatomy
To set up the file upload component correctly, you'll need to understand its anatomy and how we name its parts.
Each part includes a
data-partattribute to help identify them in the DOM.
Examples
Learn how to use the FileUpload component in your project. Let's take a look at the most basic example:
import { FileUpload } from '@ark-ui/react/file-upload'
import { FileIcon } from 'lucide-react'
export const Basic = () => {
return (
<FileUpload.Root maxFiles={5}>
<FileUpload.Label>File Upload</FileUpload.Label>
<FileUpload.Dropzone>Drag your file(s) here</FileUpload.Dropzone>
<FileUpload.Trigger>Choose file(s)</FileUpload.Trigger>
<FileUpload.ItemGroup>
<FileUpload.Context>
{({ acceptedFiles }) =>
acceptedFiles.map((file) => (
<FileUpload.Item key={file.name} file={file}>
<FileUpload.ItemPreview type="image/*">
<FileUpload.ItemPreviewImage />
</FileUpload.ItemPreview>
<FileUpload.ItemPreview type=".*">
<FileIcon />
</FileUpload.ItemPreview>
<FileUpload.ItemName />
<FileUpload.ItemSizeText />
<FileUpload.ItemDeleteTrigger>X</FileUpload.ItemDeleteTrigger>
</FileUpload.Item>
))
}
</FileUpload.Context>
</FileUpload.ItemGroup>
<FileUpload.HiddenInput />
</FileUpload.Root>
)
}
import { FileUpload } from '@ark-ui/solid/file-upload'
import { For } from 'solid-js'
export const Basic = () => (
<FileUpload.Root maxFiles={5}>
<FileUpload.Label>File Upload</FileUpload.Label>
<FileUpload.Dropzone>Drag your file(s) here</FileUpload.Dropzone>
<FileUpload.Trigger>Choose file(s)</FileUpload.Trigger>
<FileUpload.ItemGroup>
<FileUpload.Context>
{(context) => (
<For each={context().acceptedFiles}>
{(file) => (
<FileUpload.Item file={file}>
<FileUpload.ItemPreview type="image/*">
<FileUpload.ItemPreviewImage />
</FileUpload.ItemPreview>
<FileUpload.ItemPreview type=".*">Any Icon</FileUpload.ItemPreview>
<FileUpload.ItemName />
<FileUpload.ItemSizeText />
<FileUpload.ItemDeleteTrigger>X</FileUpload.ItemDeleteTrigger>
</FileUpload.Item>
)}
</For>
)}
</FileUpload.Context>
</FileUpload.ItemGroup>
<FileUpload.HiddenInput />
</FileUpload.Root>
)
<script setup lang="ts">
import { FileUpload } from '@ark-ui/vue/file-upload'
</script>
<template>
<FileUpload.Root :maxFiles="5">
<FileUpload.Label>File Upload</FileUpload.Label>
<FileUpload.Dropzone>Drop your files here</FileUpload.Dropzone>
<FileUpload.Trigger>Choose file(s)</FileUpload.Trigger>
<FileUpload.ItemGroup>
<FileUpload.Context v-slot="{ acceptedFiles }">
<FileUpload.Item v-for="file in acceptedFiles" :file="file" :key="file.name">
<FileUpload.ItemPreview type="image/*">
<FileUpload.ItemPreviewImage />
</FileUpload.ItemPreview>
<FileUpload.ItemPreview type=".*">
<div>Generic Icon</div>
</FileUpload.ItemPreview>
<FileUpload.ItemName />
<FileUpload.ItemSizeText />
<FileUpload.ItemDeleteTrigger>X</FileUpload.ItemDeleteTrigger>
</FileUpload.Item>
</FileUpload.Context>
</FileUpload.ItemGroup>
<FileUpload.HiddenInput />
</FileUpload.Root>
</template>
Validation
Use the validate prop to validate the files before they are uploaded. The return value of the function should map to:
TOO_MANY_FILES: If the user tries to upload more files than themaxFilesprop allows.FILE_INVALID_TYPE: If the file is of an invalid type.FILE_TOO_LARGE: If the file is too large.FILE_TOO_SMALL: If the file is too small.FILE_INVALID: If the file is invalid (for any other reason).FILE_EXISTS: If the file already exists.
import { FileUpload } from '@ark-ui/react/file-upload'
import { FileIcon } from 'lucide-react'
export const WithValidation = () => {
return (
<FileUpload.Root
validate={(file) => {
if (file.name.length > 20) return ['FILE_NAME_TOO_LONG']
return null
}}
>
<FileUpload.Trigger>Choose file(s)</FileUpload.Trigger>
<FileUpload.ItemGroup>
<FileUpload.Context>
{({ acceptedFiles }) =>
acceptedFiles.map((file) => (
<FileUpload.Item key={file.name} file={file}>
<FileUpload.ItemPreview type="image/*">
<FileUpload.ItemPreviewImage />
</FileUpload.ItemPreview>
<FileUpload.ItemPreview type=".*">
<FileIcon />
</FileUpload.ItemPreview>
<FileUpload.ItemName />
<FileUpload.ItemSizeText />
<FileUpload.ItemDeleteTrigger>X</FileUpload.ItemDeleteTrigger>
</FileUpload.Item>
))
}
</FileUpload.Context>
</FileUpload.ItemGroup>
<FileUpload.HiddenInput />
</FileUpload.Root>
)
}
import { FileUpload } from '@ark-ui/solid/file-upload'
import { FileIcon } from 'lucide-solid'
import { For } from 'solid-js'
export const WithValidation = () => {
return (
<FileUpload.Root
validate={(file) => {
if (file.name.length > 20) return ['FILE_NAME_TOO_LONG']
return null
}}
>
<FileUpload.Trigger>Choose file(s)</FileUpload.Trigger>
<FileUpload.ItemGroup>
<FileUpload.Context>
{(context) => (
<For each={context().acceptedFiles}>
{(file) => (
<FileUpload.Item file={file}>
<FileUpload.ItemPreview type="image/*">
<FileUpload.ItemPreviewImage />
</FileUpload.ItemPreview>
<FileUpload.ItemPreview type=".*">
<FileIcon />
</FileUpload.ItemPreview>
<FileUpload.ItemName />
<FileUpload.ItemSizeText />
<FileUpload.ItemDeleteTrigger>X</FileUpload.ItemDeleteTrigger>
</FileUpload.Item>
)}
</For>
)}
</FileUpload.Context>
</FileUpload.ItemGroup>
<FileUpload.HiddenInput />
</FileUpload.Root>
)
}
<script setup lang="ts">
import { FileUpload } from '@ark-ui/vue/file-upload'
import { FileIcon } from 'lucide-vue-next'
</script>
<template>
<FileUpload.Root
:validate="
(file) => {
if (file.name.length > 20) return ['FILE_NAME_TOO_LONG']
return null
}
"
>
<FileUpload.Trigger>Choose file(s)</FileUpload.Trigger>
<FileUpload.ItemGroup>
<FileUpload.Context v-slot="{ acceptedFiles }">
<FileUpload.Item v-for="file in acceptedFiles" :file="file" :key="file.name">
<FileUpload.ItemPreview type="image/*">
<FileUpload.ItemPreviewImage />
</FileUpload.ItemPreview>
<FileUpload.ItemPreview type=".*">
<FileIcon />
</FileUpload.ItemPreview>
<FileUpload.ItemName />
<FileUpload.ItemSizeText />
<FileUpload.ItemDeleteTrigger>X</FileUpload.ItemDeleteTrigger>
</FileUpload.Item>
</FileUpload.Context>
</FileUpload.ItemGroup>
<FileUpload.HiddenInput />
</FileUpload.Root>
</template>
Field
Here's an example of how to use the FileUpload component with the Field component to provide a error and helper
text.
import { Field } from '@ark-ui/react/field'
import { FileUpload } from '@ark-ui/react/file-upload'
export const WithField = (props: Field.RootProps) => (
<Field.Root {...props}>
<FileUpload.Root maxFiles={5}>
<FileUpload.Label>Label</FileUpload.Label>
<FileUpload.Trigger>Select</FileUpload.Trigger>
<FileUpload.ItemGroup />
<FileUpload.HiddenInput data-testid="input" />
</FileUpload.Root>
<Field.HelperText>Additional Info</Field.HelperText>
<Field.ErrorText>Error Info</Field.ErrorText>
</Field.Root>
)
import { Field } from '@ark-ui/solid/field'
import { FileUpload } from '@ark-ui/solid/file-upload'
export const WithField = (props: Field.RootProps) => (
<Field.Root {...props}>
<FileUpload.Root maxFiles={5}>
<FileUpload.Label>Label</FileUpload.Label>
<FileUpload.Trigger>Select</FileUpload.Trigger>
<FileUpload.ItemGroup />
<FileUpload.HiddenInput data-testid="input" />
</FileUpload.Root>
<Field.HelperText>Additional Info</Field.HelperText>
<Field.ErrorText>Error Info</Field.ErrorText>
</Field.Root>
)
<script setup lang="ts">
import { Field } from '@ark-ui/vue/field'
import { FileUpload } from '@ark-ui/vue/file-upload'
</script>
<template>
<Field.Root>
<FileUpload.Root :maxFiles="5">
<FileUpload.Label>Label</FileUpload.Label>
<FileUpload.Trigger>Select</FileUpload.Trigger>
<FileUpload.ItemGroup />
<FileUpload.HiddenInput data-testid="input" />
</FileUpload.Root>
<Field.HelperText>Additional Info</Field.HelperText>
<Field.ErrorText>Error Info</Field.ErrorText>
</Field.Root>
</template>
Root Provider
Use the useFileUpload hook to create the file upload store and pass it to the FileUpload.RootProvider component.
This allows you to have maximum control over the file upload programmatically.
import { FileUpload, useFileUpload } from '@ark-ui/react/file-upload'
import { FileIcon } from 'lucide-react'
export const RootProvider = () => {
const fileUpload = useFileUpload({ maxFiles: 5 })
return (
<>
<button onClick={() => fileUpload.clearFiles()}>Clear</button>
<FileUpload.RootProvider value={fileUpload}>
<FileUpload.Label>File Upload</FileUpload.Label>
<FileUpload.Dropzone>Drag your file(s) here</FileUpload.Dropzone>
<FileUpload.Trigger>Choose file(s)</FileUpload.Trigger>
<FileUpload.ItemGroup>
<FileUpload.Context>
{({ acceptedFiles }) =>
acceptedFiles.map((file) => (
<FileUpload.Item key={file.name} file={file}>
<FileUpload.ItemPreview type="image/*">
<FileUpload.ItemPreviewImage />
</FileUpload.ItemPreview>
<FileUpload.ItemPreview type=".*">
<FileIcon />
</FileUpload.ItemPreview>
<FileUpload.ItemName />
<FileUpload.ItemSizeText />
<FileUpload.ItemDeleteTrigger>X</FileUpload.ItemDeleteTrigger>
</FileUpload.Item>
))
}
</FileUpload.Context>
</FileUpload.ItemGroup>
<FileUpload.HiddenInput />
</FileUpload.RootProvider>
</>
)
}
import { FileUpload, useFileUpload } from '@ark-ui/solid/file-upload'
import { For } from 'solid-js'
export const RootProvider = () => {
const fileUpload = useFileUpload({ maxFiles: 5 })
return (
<>
<button onClick={() => fileUpload().clearFiles()}>Clear</button>
<FileUpload.RootProvider value={fileUpload}>
<FileUpload.Label>File Upload</FileUpload.Label>
<FileUpload.Dropzone>Drag your file(s)here</FileUpload.Dropzone>
<FileUpload.Trigger>Choose file(s)</FileUpload.Trigger>
<FileUpload.ItemGroup>
<FileUpload.Context>
{(context) => (
<For each={context().acceptedFiles}>
{(file) => (
<FileUpload.Item file={file}>
<FileUpload.ItemPreview type="image/*">
<FileUpload.ItemPreviewImage />
</FileUpload.ItemPreview>
<FileUpload.ItemPreview type=".*">Any Icon</FileUpload.ItemPreview>
<FileUpload.ItemName />
<FileUpload.ItemSizeText />
<FileUpload.ItemDeleteTrigger>X</FileUpload.ItemDeleteTrigger>
</FileUpload.Item>
)}
</For>
)}
</FileUpload.Context>
</FileUpload.ItemGroup>
<FileUpload.HiddenInput />
</FileUpload.RootProvider>
</>
)
}
<script setup lang="ts">
import { FileUpload, useFileUpload } from '@ark-ui/vue/file-upload'
const fileUpload = useFileUpload({ maxFiles: 5 })
</script>
<template>
<button @click="fileUpload.clearFiles()">Clear</button>
<FileUpload.RootProvider :value="fileUpload">
<FileUpload.Label>File Upload</FileUpload.Label>
<FileUpload.Dropzone>Drop your files here</FileUpload.Dropzone>
<FileUpload.Trigger>Choose file(s)</FileUpload.Trigger>
<FileUpload.ItemGroup>
<FileUpload.Context v-slot="{ acceptedFiles }">
<FileUpload.Item v-for="file in acceptedFiles" :file="file" :key="file.name">
<FileUpload.ItemPreview type="image/*">
<FileUpload.ItemPreviewImage />
</FileUpload.ItemPreview>
<FileUpload.ItemPreview type=".*">
<div>Generic Icon</div>
</FileUpload.ItemPreview>
<FileUpload.ItemName />
<FileUpload.ItemSizeText />
<FileUpload.ItemDeleteTrigger>X</FileUpload.ItemDeleteTrigger>
</FileUpload.Item>
</FileUpload.Context>
</FileUpload.ItemGroup>
<FileUpload.HiddenInput />
</FileUpload.RootProvider>
</template>
If you're using the
RootProvidercomponent, you don't need to use theRootcomponent.
API Reference
Root
| Prop | Default | Type |
|---|---|---|
accept | Record<string, string[]> | FileMimeType | FileMimeType[]The accept file types | |
allowDrop | true | booleanWhether to allow drag and drop in the dropzone element |
asChild | booleanUse the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. | |
capture | 'user' | 'environment'The default camera to use when capturing media | |
directory | booleanWhether to accept directories, only works in webkit browsers | |
disabled | booleanWhether the file input is disabled | |
ids | Partial<{
root: string
dropzone: string
hiddenInput: string
trigger: string
label: string
item(id: string): string
itemName(id: string): string
itemSizeText(id: string): string
itemPreview(id: string): string
}>The ids of the elements. Useful for composition. | |
invalid | booleanWhether the file input is invalid | |
locale | 'en-US' | stringThe current locale. Based on the BCP 47 definition. |
maxFiles | 1 | numberThe maximum number of files |
maxFileSize | Infinity | numberThe maximum file size in bytes |
minFileSize | 0 | numberThe minimum file size in bytes |
name | stringThe name of the underlying file input | |
onFileAccept | (details: FileAcceptDetails) => voidFunction called when the file is accepted | |
onFileChange | (details: FileChangeDetails) => voidFunction called when the value changes, whether accepted or rejected | |
onFileReject | (details: FileRejectDetails) => voidFunction called when the file is rejected | |
preventDocumentDrop | true | booleanWhether to prevent the drop event on the document |
required | booleanWhether the file input is required | |
transformFiles | (files: File[]) => Promise<File[]>Function to transform the accepted files to apply transformations | |
translations | IntlTranslationsThe localized messages to use. | |
validate | (file: File, details: FileValidateDetails) => FileError[] | nullFunction to validate a file |
| Data Attribute | Value |
|---|---|
[data-scope] | file-upload |
[data-part] | root |
[data-disabled] | Present when disabled |
[data-dragging] | Present when in the dragging state |
ClearTrigger
| Prop | Default | Type |
|---|---|---|
asChild | booleanUse the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. |
| Data Attribute | Value |
|---|---|
[data-scope] | file-upload |
[data-part] | clear-trigger |
[data-disabled] | Present when disabled |
Dropzone
| Prop | Default | Type |
|---|---|---|
asChild | booleanUse the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. | |
disableClick | booleanWhether to disable the click event on the dropzone |
| Data Attribute | Value |
|---|---|
[data-scope] | file-upload |
[data-part] | dropzone |
[data-invalid] | Present when invalid |
[data-disabled] | Present when disabled |
[data-dragging] | Present when in the dragging state |
HiddenInput
| Prop | Default | Type |
|---|---|---|
asChild | booleanUse the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. |
ItemDeleteTrigger
| Prop | Default | Type |
|---|---|---|
asChild | booleanUse the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. |
| Data Attribute | Value |
|---|---|
[data-scope] | file-upload |
[data-part] | item-delete-trigger |
[data-disabled] | Present when disabled |
ItemGroup
| Prop | Default | Type |
|---|---|---|
asChild | booleanUse the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. |
| Data Attribute | Value |
|---|---|
[data-scope] | file-upload |
[data-part] | item-group |
[data-disabled] | Present when disabled |
ItemName
| Prop | Default | Type |
|---|---|---|
asChild | booleanUse the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. |
| Data Attribute | Value |
|---|---|
[data-scope] | file-upload |
[data-part] | item-name |
[data-disabled] | Present when disabled |
ItemPreviewImage
| Prop | Default | Type |
|---|---|---|
asChild | booleanUse the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. |
| Data Attribute | Value |
|---|---|
[data-scope] | file-upload |
[data-part] | item-preview-image |
[data-disabled] | Present when disabled |
ItemPreview
| Prop | Default | Type |
|---|---|---|
asChild | booleanUse the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. | |
type | '.*' | stringThe file type to match against. Matches all file types by default. |
| Data Attribute | Value |
|---|---|
[data-scope] | file-upload |
[data-part] | item-preview |
[data-disabled] | Present when disabled |
Item
| Prop | Default | Type |
|---|---|---|
file | File | |
asChild | booleanUse the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. |
| Data Attribute | Value |
|---|---|
[data-scope] | file-upload |
[data-part] | item |
[data-disabled] | Present when disabled |
ItemSizeText
| Prop | Default | Type |
|---|---|---|
asChild | booleanUse the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. |
| Data Attribute | Value |
|---|---|
[data-scope] | file-upload |
[data-part] | item-size-text |
[data-disabled] | Present when disabled |
Label
| Prop | Default | Type |
|---|---|---|
asChild | booleanUse the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. |
| Data Attribute | Value |
|---|---|
[data-scope] | file-upload |
[data-part] | label |
[data-disabled] | Present when disabled |
RootProvider
| Prop | Default | Type |
|---|---|---|
value | UseFileUploadReturn | |
asChild | booleanUse the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. |
Trigger
| Prop | Default | Type |
|---|---|---|
asChild | booleanUse the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. |
| Data Attribute | Value |
|---|---|
[data-scope] | file-upload |
[data-part] | trigger |
[data-disabled] | Present when disabled |
[data-invalid] | Present when invalid |