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-part
attribute 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.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.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.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>
<script lang="ts">
import { FileUpload } from '@ark-ui/svelte/file-upload'
import { FileIcon } from 'lucide-svelte'
</script>
<FileUpload.Root maxFiles={5}>
<FileUpload.Label>File Upload</FileUpload.Label>
<FileUpload.Trigger>Choose file(s)</FileUpload.Trigger>
<FileUpload.ItemGroup>
<FileUpload.Context>
{#snippet render(context)}
{#each context().acceptedFiles as file (file.name)}
<FileUpload.Item {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>
{/each}
{/snippet}
</FileUpload.Context>
</FileUpload.ItemGroup>
<FileUpload.HiddenInput />
</FileUpload.Root>
Initial Files
Use the defaultAcceptedFiles
prop to set the initial files in the file upload component.
import { FileUpload } from '@ark-ui/react/file-upload'
import { FileIcon } from 'lucide-react'
export const InitialFiles = () => {
return (
<FileUpload.Root
defaultAcceptedFiles={[new File(['Welcome to Ark UI React`'], 'README.md', { type: 'text/plain' })]}
>
<FileUpload.Label>File Upload</FileUpload.Label>
<FileUpload.Trigger>Choose file(s)</FileUpload.Trigger>
<FileUpload.ItemGroup>
<FileUpload.Context>
{({ acceptedFiles }) =>
acceptedFiles.map((file) => (
<FileUpload.Item key={file.name} file={file}>
<FileIcon />
<FileUpload.ItemName />
<FileUpload.ItemSizeText />
</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 InitialFiles = () => (
<FileUpload.Root defaultAcceptedFiles={[new File(['Welcome to Ark UI Solid'], 'README.md', { type: 'text/plain' })]}>
<FileUpload.Label>File Upload</FileUpload.Label>
<FileUpload.Trigger>Choose file(s)</FileUpload.Trigger>
<FileUpload.ItemGroup>
<FileUpload.Context>
{(context) => (
<For each={context().acceptedFiles}>
{(file) => (
<FileUpload.Item file={file}>
<div>📄</div>
<FileUpload.ItemName />
<FileUpload.ItemSizeText />
</FileUpload.Item>
)}
</For>
)}
</FileUpload.Context>
</FileUpload.ItemGroup>
<FileUpload.HiddenInput />
</FileUpload.Root>
)
<script setup lang="ts">
import { FileUpload } from '@ark-ui/vue/file-upload'
const defaultAcceptedFiles = [new File(['Welcome to Ark UI Vue'], 'README.md', { type: 'text/plain' })]
</script>
<template>
<FileUpload.Root :default-accepted-files="defaultAcceptedFiles">
<FileUpload.Label>File Upload</FileUpload.Label>
<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">
<div>📄</div>
<FileUpload.ItemName />
<FileUpload.ItemSizeText />
</FileUpload.Item>
</FileUpload.Context>
</FileUpload.ItemGroup>
<FileUpload.HiddenInput />
</FileUpload.Root>
</template>
<script lang="ts">
import { FileUpload } from '@ark-ui/svelte/file-upload'
</script>
<FileUpload.Root
defaultAcceptedFiles={[new File(['Welcome to Ark UI Svelte'], 'README.md', { type: 'text/plain' })]}
>
<FileUpload.Label>File Upload</FileUpload.Label>
<FileUpload.Trigger>Choose file(s)</FileUpload.Trigger>
<FileUpload.ItemGroup>
<FileUpload.Context>
{#snippet render(context)}
{#each context().acceptedFiles as file (file.name)}
<FileUpload.Item {file}>
<div>📄</div>
<FileUpload.ItemName />
<FileUpload.ItemSizeText />
</FileUpload.Item>
{/each}
{/snippet}
</FileUpload.Context>
</FileUpload.ItemGroup>
<FileUpload.HiddenInput />
</FileUpload.Root>
Clear Trigger
Use the ClearTrigger
component to provide users with a way to remove all uploaded files at once. This trigger will
clear both accepted and rejected files from the upload component.
import { FileUpload } from '@ark-ui/react/file-upload'
export const ClearTrigger = () => {
return (
<FileUpload.Root maxFiles={5} accept="image/png,image/jpeg">
<FileUpload.Label>File Upload</FileUpload.Label>
<FileUpload.Trigger>Choose file(s)</FileUpload.Trigger>
<FileUpload.ClearTrigger>Clear Files</FileUpload.ClearTrigger>
<FileUpload.ItemGroup>
<FileUpload.Context>
{({ acceptedFiles }) =>
acceptedFiles.map((file) => (
<FileUpload.Item key={file.name} file={file}>
<FileUpload.ItemPreview type="image/*">
<FileUpload.ItemPreviewImage />
</FileUpload.ItemPreview>
<FileUpload.ItemName />
</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 ClearTrigger = () => (
<FileUpload.Root maxFiles={5} accept="image/png,image/jpeg">
<FileUpload.Label>File Upload</FileUpload.Label>
<FileUpload.Trigger>Choose file(s)</FileUpload.Trigger>
<FileUpload.ClearTrigger>Clear Files</FileUpload.ClearTrigger>
<FileUpload.ItemGroup>
<FileUpload.Context>
{(context) => (
<For each={context().acceptedFiles}>
{(file) => (
<FileUpload.Item file={file}>
<FileUpload.ItemPreview type="image/*">
<FileUpload.ItemPreviewImage />
</FileUpload.ItemPreview>
<FileUpload.ItemName />
</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" accept="image/png,image/jpeg">
<FileUpload.Label>File Upload</FileUpload.Label>
<FileUpload.Trigger>Choose file(s)</FileUpload.Trigger>
<FileUpload.ClearTrigger>Clear Files</FileUpload.ClearTrigger>
<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.ItemName />
</FileUpload.Item>
</FileUpload.Context>
</FileUpload.ItemGroup>
<FileUpload.HiddenInput />
</FileUpload.Root>
</template>
<script lang="ts">
import { FileUpload } from '@ark-ui/svelte/file-upload'
import { FileIcon } from 'lucide-svelte'
</script>
<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>
<!-- Custom Clear Trigger -->
<FileUpload.Context>
{#snippet render(context)}
{#if context().acceptedFiles.length > 0}
<button type="button" onclick={() => context().clearFiles()}>Clear Files</button>
{/if}
{/snippet}
</FileUpload.Context>
<FileUpload.ItemGroup>
<FileUpload.Context>
{#snippet render(context)}
{#each context().acceptedFiles as file (file.name)}
<FileUpload.Item {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>
{/each}
{/snippet}
</FileUpload.Context>
</FileUpload.ItemGroup>
<FileUpload.HiddenInput />
</FileUpload.Root>
Drag & Drop
Use the Dropzone
component to enable drag-and-drop functionality. The dropzone provides adds a data-dragging
attribute while dragging for styling purposes.
import { FileUpload } from '@ark-ui/react/file-upload'
export const DragAndDrop = () => {
return (
<FileUpload.Root accept="image/*" maxFiles={3}>
<FileUpload.Dropzone>Drag and drop your images here</FileUpload.Dropzone>
<FileUpload.ItemGroup>
<FileUpload.Context>
{({ acceptedFiles }) =>
acceptedFiles.map((file) => (
<FileUpload.Item key={file.name} file={file} className="file-item">
<FileUpload.ItemPreview type="image/*">
<FileUpload.ItemPreviewImage />
</FileUpload.ItemPreview>
<FileUpload.ItemName />
</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 DragAndDrop = () => (
<FileUpload.Root accept="image/*" maxFiles={3}>
<FileUpload.Dropzone>Drag and drop your images here</FileUpload.Dropzone>
<FileUpload.ItemGroup>
<FileUpload.Context>
{(fileUpload) => (
<For each={fileUpload().acceptedFiles}>
{(file) => (
<FileUpload.Item file={file} class="file-item">
<FileUpload.ItemPreview type="image/*">
<FileUpload.ItemPreviewImage />
</FileUpload.ItemPreview>
<FileUpload.ItemName />
</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 accept="image/*" :maxFiles="3">
<FileUpload.Dropzone>Drag and drop your images here</FileUpload.Dropzone>
<FileUpload.ItemGroup>
<FileUpload.Context v-slot="{ acceptedFiles }">
<FileUpload.Item v-for="file in acceptedFiles" :file="file" :key="file.name" class="file-item">
<FileUpload.ItemPreview type="image/*">
<FileUpload.ItemPreviewImage />
</FileUpload.ItemPreview>
<FileUpload.ItemName />
</FileUpload.Item>
</FileUpload.Context>
</FileUpload.ItemGroup>
<FileUpload.HiddenInput />
</FileUpload.Root>
</template>
<script lang="ts">
import { FileUpload } from '@ark-ui/svelte/file-upload'
</script>
<FileUpload.Root accept="image/*" maxFiles={3}>
<FileUpload.Dropzone>
Drag and drop your images here
</FileUpload.Dropzone>
<FileUpload.ItemGroup>
<FileUpload.Context>
{#snippet render(fileUpload)}
{#each fileUpload().acceptedFiles as file (file.name)}
<FileUpload.Item {file} class="file-item">
<FileUpload.ItemPreview type="image/*">
<FileUpload.ItemPreviewImage />
</FileUpload.ItemPreview>
<FileUpload.ItemName />
</FileUpload.Item>
{/each}
{/snippet}
</FileUpload.Context>
</FileUpload.ItemGroup>
<FileUpload.HiddenInput />
</FileUpload.Root>
Directory Upload
Use the directory
prop to allow users to upload entire folders. This enables selecting multiple files from a directory
structure while preserving the folder hierarchy.
import { FileUpload } from '@ark-ui/react/file-upload'
export const DirectoryUpload = () => {
return (
<FileUpload.Root directory>
<FileUpload.Trigger>Upload Folder</FileUpload.Trigger>
<FileUpload.ItemGroup>
<FileUpload.Context>
{({ acceptedFiles }) =>
acceptedFiles.map((file) => (
<FileUpload.Item key={file.name} file={file}>
<FileUpload.ItemName>{file.webkitRelativePath ?? file.name}</FileUpload.ItemName>
</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 DirectoryUpload = () => (
<FileUpload.Root directory>
<FileUpload.Trigger>Upload Folder</FileUpload.Trigger>
<FileUpload.ItemGroup>
<FileUpload.Context>
{(fileUpload) => (
<For each={fileUpload().acceptedFiles}>
{(file) => (
<FileUpload.Item file={file}>
<FileUpload.ItemName>{file.webkitRelativePath ?? file.name}</FileUpload.ItemName>
</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 directory>
<FileUpload.Trigger>Upload Folder</FileUpload.Trigger>
<FileUpload.ItemGroup>
<FileUpload.Context v-slot="{ acceptedFiles }">
<FileUpload.Item v-for="file in acceptedFiles" :file="file" :key="file.name">
<FileUpload.ItemName>{{ file.webkitRelativePath ?? file.name }}</FileUpload.ItemName>
</FileUpload.Item>
</FileUpload.Context>
</FileUpload.ItemGroup>
<FileUpload.HiddenInput />
</FileUpload.Root>
</template>
<script lang="ts">
import { FileUpload } from '@ark-ui/svelte/file-upload'
</script>
<FileUpload.Root directory>
<FileUpload.Trigger>Upload Folder</FileUpload.Trigger>
<FileUpload.ItemGroup>
<FileUpload.Context>
{#snippet render(fileUpload)}
{#each fileUpload().acceptedFiles as file (file.name)}
<FileUpload.Item {file}>
<FileUpload.ItemName>
{file.webkitRelativePath ?? file.name}
</FileUpload.ItemName>
</FileUpload.Item>
{/each}
{/snippet}
</FileUpload.Context>
</FileUpload.ItemGroup>
<FileUpload.HiddenInput />
</FileUpload.Root>
The file.webkitRelativePath
property contains the full path of each file within the uploaded directory, allowing you
to display the folder structure or process files based on their location.
Accepted File Types
Use the accept
prop to restrict the file types that can be uploaded. This prop accepts MIME types (e.g., image/png
,
image/jpeg
) or file extensions (e.g., .pdf
, .txt
).
When users attempt to upload files that don't match the accepted types, these files will be automatically rejected and
appear in the rejectedFiles
array with a FILE_INVALID_TYPE
error code.
import { FileUpload } from '@ark-ui/react/file-upload'
export const AcceptedFileTypes = () => {
return (
<FileUpload.Root accept="image/png,image/jpeg">
<FileUpload.Label>File Upload (PNG and JPEG only)</FileUpload.Label>
<FileUpload.Dropzone>Drop your files here</FileUpload.Dropzone>
<FileUpload.Trigger>Select Files</FileUpload.Trigger>
<FileUpload.ItemGroup>
<FileUpload.Context>
{({ acceptedFiles }) =>
acceptedFiles.map((file) => (
<FileUpload.Item key={file.name} file={file}>
<FileUpload.ItemName />
<FileUpload.ItemSizeText />
<FileUpload.ItemDeleteTrigger>Remove</FileUpload.ItemDeleteTrigger>
</FileUpload.Item>
))
}
</FileUpload.Context>
</FileUpload.ItemGroup>
<FileUpload.ItemGroup>
<FileUpload.Context>
{({ rejectedFiles }) =>
rejectedFiles.map((fileRejection) => (
<FileUpload.Item key={fileRejection.file.name} file={fileRejection.file}>
<FileUpload.ItemName />
<FileUpload.ItemSizeText />
<div>
{fileRejection.errors.map((error) => (
<div key={error} style={{ color: 'red' }}>
{error}
</div>
))}
</div>
</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 AcceptedFileTypes = () => (
<FileUpload.Root accept="image/png,image/jpeg">
<FileUpload.Label>File Upload (PNG and JPEG only)</FileUpload.Label>
<FileUpload.Dropzone>Drop your files here</FileUpload.Dropzone>
<FileUpload.Trigger>Select Files</FileUpload.Trigger>
<FileUpload.ItemGroup>
<FileUpload.Context>
{(context) => (
<For each={context().acceptedFiles}>
{(file) => (
<FileUpload.Item file={file}>
<FileUpload.ItemName />
<FileUpload.ItemSizeText />
<FileUpload.ItemDeleteTrigger>Remove</FileUpload.ItemDeleteTrigger>
</FileUpload.Item>
)}
</For>
)}
</FileUpload.Context>
</FileUpload.ItemGroup>
<FileUpload.ItemGroup>
<FileUpload.Context>
{(context) => (
<For each={context().rejectedFiles}>
{(fileRejection) => (
<FileUpload.Item file={fileRejection.file}>
<FileUpload.ItemName />
<FileUpload.ItemSizeText />
<div>
<For each={fileRejection.errors}>{(error) => <div style={{ color: 'red' }}>{error}</div>}</For>
</div>
</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 accept="image/png,image/jpeg">
<FileUpload.Label>File Upload (PNG and JPEG only)</FileUpload.Label>
<FileUpload.Dropzone>Drop your files here</FileUpload.Dropzone>
<FileUpload.Trigger>Select Files</FileUpload.Trigger>
<FileUpload.ItemGroup>
<FileUpload.Context v-slot="{ acceptedFiles }">
<FileUpload.Item v-for="file in acceptedFiles" :file="file" :key="file.name">
<FileUpload.ItemName />
<FileUpload.ItemSizeText />
<FileUpload.ItemDeleteTrigger>Remove</FileUpload.ItemDeleteTrigger>
</FileUpload.Item>
</FileUpload.Context>
</FileUpload.ItemGroup>
<FileUpload.ItemGroup>
<FileUpload.Context v-slot="{ rejectedFiles }">
<FileUpload.Item
v-for="fileRejection in rejectedFiles"
:file="fileRejection.file"
:key="fileRejection.file.name"
>
<FileUpload.ItemName />
<FileUpload.ItemSizeText />
<div>
<div v-for="error in fileRejection.errors" :key="error" style="color: red">
{{ error }}
</div>
</div>
</FileUpload.Item>
</FileUpload.Context>
</FileUpload.ItemGroup>
<FileUpload.HiddenInput />
</FileUpload.Root>
</template>
<script lang="ts">
import { FileUpload } from '@ark-ui/svelte/file-upload'
</script>
<FileUpload.Root accept="image/png,image/jpeg">
<FileUpload.Label>File Upload (PNG and JPEG only)</FileUpload.Label>
<FileUpload.Dropzone>Drop your files here</FileUpload.Dropzone>
<FileUpload.Trigger>Select Files</FileUpload.Trigger>
<FileUpload.ItemGroup>
<FileUpload.Context>
{#snippet render(context)}
{#each context().acceptedFiles as file (file.name)}
<FileUpload.Item {file}>
<FileUpload.ItemName />
<FileUpload.ItemSizeText />
<FileUpload.ItemDeleteTrigger>Remove</FileUpload.ItemDeleteTrigger>
</FileUpload.Item>
{/each}
{/snippet}
</FileUpload.Context>
</FileUpload.ItemGroup>
<FileUpload.ItemGroup>
<FileUpload.Context>
{#snippet render(context)}
{#each context().rejectedFiles as fileRejection (fileRejection.file.name)}
<FileUpload.Item file={fileRejection.file}>
<FileUpload.ItemName />
<FileUpload.ItemSizeText />
<div>
{#each fileRejection.errors as error}
<div style="color: red;">
{error}
</div>
{/each}
</div>
</FileUpload.Item>
{/each}
{/snippet}
</FileUpload.Context>
</FileUpload.ItemGroup>
<FileUpload.HiddenInput />
</FileUpload.Root>
Error Handling
The FileUpload
component provides comprehensive validation and error handling capabilities. You can set various
constraints and handle different types of validation errors:
Built-in Validation Props:
maxFiles
- Maximum number of files allowedmaxFileSize
- Maximum file size in bytesminFileSize
- Minimum file size in bytesaccept
- Allowed MIME types or file extensions
Built-in Error Types:
TOO_MANY_FILES
- Exceeds maxFiles limitFILE_INVALID_TYPE
- File type not in accept listFILE_TOO_LARGE
- File size exceeds maxFileSizeFILE_TOO_SMALL
- File size below minFileSizeFILE_INVALID
- Generic validation failureFILE_EXISTS
- Duplicate file detected
import { FileUpload, type FileUploadFileError } from '@ark-ui/react/file-upload'
const errorMessages: Record<FileUploadFileError, string> = {
TOO_MANY_FILES: '📊 Too many files selected (max 3 allowed)',
FILE_INVALID_TYPE: '🚫 Invalid file type (only images and PDFs allowed)',
FILE_TOO_LARGE: '📏 File too large (max 1MB)',
FILE_TOO_SMALL: '📐 File too small (min 1KB)',
FILE_INVALID: '⚠️ Invalid file',
FILE_EXISTS: '🔄 File already exists',
}
export const ErrorHandling = () => {
return (
<FileUpload.Root
maxFiles={3}
maxFileSize={1024 * 1024} // 1MB
minFileSize={1024} // 1KB
accept="image/*,application/pdf"
>
<FileUpload.Label>File Upload with Validation</FileUpload.Label>
<FileUpload.Trigger>Select Files</FileUpload.Trigger>
{/* Accepted Files Section */}
<div data-status="accepted">
<h3>✅ Accepted Files</h3>
<FileUpload.ItemGroup>
<FileUpload.Context>
{({ acceptedFiles }) =>
acceptedFiles.length === 0 ? (
<div>No files uploaded yet</div>
) : (
acceptedFiles.map((file) => (
<FileUpload.Item key={file.name} file={file} className="file-item" data-status="accepted">
<FileUpload.ItemPreview type="image/*">
<FileUpload.ItemPreviewImage />
</FileUpload.ItemPreview>
<FileUpload.ItemPreview type="application/pdf">
<div data-type="pdf">PDF</div>
</FileUpload.ItemPreview>
<FileUpload.ItemName />
<FileUpload.ItemSizeText />
<FileUpload.ItemDeleteTrigger>Remove</FileUpload.ItemDeleteTrigger>
</FileUpload.Item>
))
)
}
</FileUpload.Context>
</FileUpload.ItemGroup>
</div>
{/* Rejected Files Section */}
<div data-status="rejected">
<h3>❌ Rejected Files</h3>
<FileUpload.ItemGroup>
<FileUpload.Context>
{({ rejectedFiles }) =>
rejectedFiles.length === 0 ? (
<div>No rejected files</div>
) : (
rejectedFiles.map((fileRejection) => (
<FileUpload.Item
key={fileRejection.file.name}
file={fileRejection.file}
className="file-item"
data-status="rejected"
>
<FileUpload.ItemName />
<FileUpload.ItemSizeText />
<div>
<strong>Validation Errors:</strong>
{fileRejection.errors.map((error, index) => (
<div key={index} data-error-code={error}>
{errorMessages[error as FileUploadFileError] || `❓ ${error}`}
</div>
))}
</div>
</FileUpload.Item>
))
)
}
</FileUpload.Context>
</FileUpload.ItemGroup>
</div>
<FileUpload.HiddenInput />
</FileUpload.Root>
)
}
import { FileUpload, type FileUploadFileError } from '@ark-ui/solid/file-upload'
import { For } from 'solid-js'
const errorMessages: Record<FileUploadFileError, string> = {
TOO_MANY_FILES: '📊 Too many files selected (max 3 allowed)',
FILE_INVALID_TYPE: '🚫 Invalid file type (only images and PDFs allowed)',
FILE_TOO_LARGE: '📏 File too large (max 1MB)',
FILE_TOO_SMALL: '📐 File too small (min 1KB)',
FILE_INVALID: '⚠️ Invalid file',
FILE_EXISTS: '🔄 File already exists',
}
export const ErrorHandling = () => (
<FileUpload.Root
maxFiles={3}
maxFileSize={1024 * 1024} // 1MB
minFileSize={1024} // 1KB
accept="image/*,application/pdf"
>
<FileUpload.Label>File Upload with Validation</FileUpload.Label>
<FileUpload.Trigger>Select Files</FileUpload.Trigger>
{/* Accepted Files Section */}
<div data-status="accepted">
<h3>✅ Accepted Files</h3>
<FileUpload.ItemGroup>
<FileUpload.Context>
{(fileUpload) =>
fileUpload().acceptedFiles.length === 0 ? (
<div>No files uploaded yet</div>
) : (
<For each={fileUpload().acceptedFiles}>
{(file) => (
<FileUpload.Item file={file} class="file-item" data-status="accepted">
<FileUpload.ItemPreview type="image/*">
<FileUpload.ItemPreviewImage />
</FileUpload.ItemPreview>
<FileUpload.ItemPreview type="application/pdf">
<div data-type="pdf">PDF</div>
</FileUpload.ItemPreview>
<FileUpload.ItemName />
<FileUpload.ItemSizeText />
<FileUpload.ItemDeleteTrigger>Remove</FileUpload.ItemDeleteTrigger>
</FileUpload.Item>
)}
</For>
)
}
</FileUpload.Context>
</FileUpload.ItemGroup>
</div>
{/* Rejected Files Section */}
<div data-status="rejected">
<h3>❌ Rejected Files</h3>
<FileUpload.ItemGroup>
<FileUpload.Context>
{(fileUpload) =>
fileUpload().rejectedFiles.length === 0 ? (
<div>No rejected files</div>
) : (
<For each={fileUpload().rejectedFiles}>
{(fileRejection) => (
<FileUpload.Item file={fileRejection.file} class="file-item" data-status="rejected">
<FileUpload.ItemName />
<FileUpload.ItemSizeText />
<div>
<strong>Validation Errors:</strong>
<For each={fileRejection.errors}>
{(error) => <div data-error-code={error}>{errorMessages[error] || `❓ ${error}`}</div>}
</For>
</div>
</FileUpload.Item>
)}
</For>
)
}
</FileUpload.Context>
</FileUpload.ItemGroup>
</div>
<FileUpload.HiddenInput />
</FileUpload.Root>
)
<script setup lang="ts">
import { FileUpload, type FileUploadFileError } from '@ark-ui/vue/file-upload'
const errorMessages: Record<FileUploadFileError, string> = {
TOO_MANY_FILES: '📊 Too many files selected (max 3 allowed)',
FILE_INVALID_TYPE: '🚫 Invalid file type (only images and PDFs allowed)',
FILE_TOO_LARGE: '📏 File too large (max 1MB)',
FILE_TOO_SMALL: '📐 File too small (min 1KB)',
FILE_INVALID: '⚠️ Invalid file',
FILE_EXISTS: '🔄 File already exists',
}
</script>
<template>
<FileUpload.Root :maxFiles="3" :maxFileSize="1024 * 1024" :minFileSize="1024" accept="image/*,application/pdf">
<FileUpload.Label>File Upload with Validation</FileUpload.Label>
<FileUpload.Trigger>Select Files</FileUpload.Trigger>
<!-- Accepted Files Section -->
<div data-status="accepted">
<h3>✅ Accepted Files</h3>
<FileUpload.ItemGroup>
<FileUpload.Context v-slot="{ acceptedFiles }">
<div v-if="acceptedFiles.length === 0">No files uploaded yet</div>
<FileUpload.Item
v-else
v-for="file in acceptedFiles"
:file="file"
:key="file.name"
class="file-item"
data-status="accepted"
>
<FileUpload.ItemPreview type="image/*">
<FileUpload.ItemPreviewImage />
</FileUpload.ItemPreview>
<FileUpload.ItemPreview type="application/pdf">
<div data-type="pdf">PDF</div>
</FileUpload.ItemPreview>
<FileUpload.ItemName />
<FileUpload.ItemSizeText />
<FileUpload.ItemDeleteTrigger>Remove</FileUpload.ItemDeleteTrigger>
</FileUpload.Item>
</FileUpload.Context>
</FileUpload.ItemGroup>
</div>
<!-- Rejected Files Section -->
<div data-status="rejected">
<h3>❌ Rejected Files</h3>
<FileUpload.ItemGroup>
<FileUpload.Context v-slot="{ rejectedFiles }">
<div v-if="rejectedFiles.length === 0">No rejected files</div>
<FileUpload.Item
v-else
v-for="fileRejection in rejectedFiles"
:file="fileRejection.file"
:key="fileRejection.file.name"
class="file-item"
data-status="rejected"
>
<FileUpload.ItemName />
<FileUpload.ItemSizeText />
<div>
<strong>Validation Errors:</strong>
<div v-for="(error, index) in fileRejection.errors" :key="index" :data-error-code="error">
{{ errorMessages[error] || `❓ ${error}` }}
</div>
</div>
</FileUpload.Item>
</FileUpload.Context>
</FileUpload.ItemGroup>
</div>
<FileUpload.HiddenInput />
</FileUpload.Root>
</template>
<script lang="ts">
import { FileUpload, type FileUploadFileError } from '@ark-ui/svelte/file-upload'
const errorMessages: Record<FileUploadFileError, string> = {
TOO_MANY_FILES: '📊 Too many files selected (max 3 allowed)',
FILE_INVALID_TYPE: '🚫 Invalid file type (only images and PDFs allowed)',
FILE_TOO_LARGE: '📏 File too large (max 1MB)',
FILE_TOO_SMALL: '📐 File too small (min 1KB)',
FILE_INVALID: '⚠️ Invalid file',
FILE_EXISTS: '🔄 File already exists',
}
</script>
<FileUpload.Root maxFiles={3} maxFileSize={1024 * 1024} minFileSize={1024} accept="image/*,application/pdf">
<FileUpload.Label>File Upload with Validation</FileUpload.Label>
<FileUpload.Trigger>Select Files</FileUpload.Trigger>
<!-- Accepted Files Section -->
<div data-status="accepted">
<h3>✅ Accepted Files</h3>
<FileUpload.ItemGroup>
<FileUpload.Context>
{#snippet render(fileUpload)}
{#if fileUpload().acceptedFiles.length === 0}
<div>No files uploaded yet</div>
{:else}
{#each fileUpload().acceptedFiles as file (file.name)}
<FileUpload.Item {file} class="file-item" data-status="accepted">
<FileUpload.ItemPreview type="image/*">
<FileUpload.ItemPreviewImage />
</FileUpload.ItemPreview>
<FileUpload.ItemPreview type="application/pdf">
<div data-type="pdf">PDF</div>
</FileUpload.ItemPreview>
<FileUpload.ItemName />
<FileUpload.ItemSizeText />
<FileUpload.ItemDeleteTrigger>Remove</FileUpload.ItemDeleteTrigger>
</FileUpload.Item>
{/each}
{/if}
{/snippet}
</FileUpload.Context>
</FileUpload.ItemGroup>
</div>
<!-- Rejected Files Section -->
<div data-status="rejected">
<h3>❌ Rejected Files</h3>
<FileUpload.ItemGroup>
<FileUpload.Context>
{#snippet render(fileUpload)}
{#if fileUpload().rejectedFiles.length === 0}
<div>No rejected files</div>
{:else}
{#each fileUpload().rejectedFiles as fileRejection (fileRejection.file.name)}
<FileUpload.Item file={fileRejection.file} class="file-item" data-status="rejected">
<FileUpload.ItemName />
<FileUpload.ItemSizeText />
<div>
<strong>Validation Errors:</strong>
{#each fileRejection.errors as error}
<div data-error-code={error}>
{errorMessages[error] || `❓ ${error}`}
</div>
{/each}
</div>
</FileUpload.Item>
{/each}
{/if}
{/snippet}
</FileUpload.Context>
</FileUpload.ItemGroup>
</div>
<FileUpload.HiddenInput />
</FileUpload.Root>
File Transformations
Use the transformFiles
prop to process files before they're added to the accepted files list. This is useful for file
compression, format conversion, or adding metadata.
Common transformation use cases:
- Image compression: Use
image-conversion
,browser-image-compression
, or similar libraries - Format conversion: Convert files to different formats (e.g., HEIC to JPEG)
- Metadata extraction: Add EXIF data or other file information
- File validation: Perform additional checks beyond basic validation
- Resizing: Automatically resize images to specific dimensions
import { FileUpload } from '@ark-ui/react/file-upload'
import { compressAccurately } from 'image-conversion'
export const FileTransformations = () => {
const transformFiles = async (files: File[]) => {
return Promise.all(
files.map(async (file) => {
if (file.type.startsWith('image/')) {
try {
// Compress image to ~200KB
const blob = await compressAccurately(file, 200)
return new File([blob], file.name, { type: blob.type })
} catch (error) {
console.error('Compression failed for:', file.name, error)
return file
}
}
return file // Return non-image files as-is
}),
)
}
return (
<FileUpload.Root accept="image/*" maxFiles={5} transformFiles={transformFiles}>
<FileUpload.Label>File Upload with Compression</FileUpload.Label>
<FileUpload.Trigger>Choose Images</FileUpload.Trigger>
<FileUpload.ItemGroup>
<FileUpload.Context>
{({ acceptedFiles }) =>
acceptedFiles.map((file) => (
<FileUpload.Item key={file.name} file={file} className="file-item">
<FileUpload.ItemPreview type="image/*">
<FileUpload.ItemPreviewImage />
</FileUpload.ItemPreview>
<FileUpload.ItemName />
<FileUpload.ItemSizeText />
<FileUpload.ItemDeleteTrigger>Remove</FileUpload.ItemDeleteTrigger>
</FileUpload.Item>
))
}
</FileUpload.Context>
</FileUpload.ItemGroup>
<FileUpload.HiddenInput />
</FileUpload.Root>
)
}
import { FileUpload } from '@ark-ui/solid/file-upload'
import { compressAccurately } from 'image-conversion'
import { For } from 'solid-js'
export const FileTransformations = () => {
const transformFiles = async (files: File[]) => {
return Promise.all(
files.map(async (file) => {
if (file.type.startsWith('image/')) {
try {
const blob = await compressAccurately(file, 200)
return new File([blob], file.name, { type: blob.type })
} catch (error) {
console.error('Compression failed for:', file.name, error)
return file
}
}
return file
}),
)
}
return (
<FileUpload.Root accept="image/*" maxFiles={5} transformFiles={transformFiles}>
<FileUpload.Label>File Upload with Compression</FileUpload.Label>
<FileUpload.Trigger>Choose Images</FileUpload.Trigger>
<FileUpload.ItemGroup>
<FileUpload.Context>
{(fileUpload) => (
<For each={fileUpload().acceptedFiles}>
{(file) => (
<FileUpload.Item file={file} class="file-item">
<FileUpload.ItemPreview type="image/*">
<FileUpload.ItemPreviewImage />
</FileUpload.ItemPreview>
<FileUpload.ItemName />
<FileUpload.ItemSizeText />
<FileUpload.ItemDeleteTrigger>Remove</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 { compressAccurately } from 'image-conversion'
const transformFiles = async (files: File[]) => {
return Promise.all(
files.map(async (file) => {
if (file.type.startsWith('image/')) {
try {
const blob = await compressAccurately(file, 200)
return new File([blob], file.name, { type: blob.type })
} catch (error) {
console.error('Compression failed for:', file.name, error)
return file
}
}
return file
}),
)
}
</script>
<template>
<FileUpload.Root accept="image/*" :maxFiles="5" :transformFiles="transformFiles">
<FileUpload.Label>File Upload with Compression</FileUpload.Label>
<FileUpload.Trigger>Choose Images</FileUpload.Trigger>
<FileUpload.ItemGroup>
<FileUpload.Context v-slot="{ acceptedFiles }">
<FileUpload.Item v-for="file in acceptedFiles" :file="file" :key="file.name" class="file-item">
<FileUpload.ItemPreview type="image/*">
<FileUpload.ItemPreviewImage />
</FileUpload.ItemPreview>
<FileUpload.ItemName />
<FileUpload.ItemSizeText />
<FileUpload.ItemDeleteTrigger>Remove</FileUpload.ItemDeleteTrigger>
</FileUpload.Item>
</FileUpload.Context>
</FileUpload.ItemGroup>
<FileUpload.HiddenInput />
</FileUpload.Root>
</template>
<script lang="ts">
import { FileUpload } from '@ark-ui/svelte/file-upload'
import { compressAccurately } from 'image-conversion'
const transformFiles = async (files: File[]) => {
return Promise.all(
files.map(async (file) => {
if (file.type.startsWith('image/')) {
try {
const blob = await compressAccurately(file, 200)
return new File([blob], file.name, { type: blob.type })
} catch (error) {
console.error('Compression failed for:', file.name, error)
return file
}
}
return file
}),
)
}
</script>
<FileUpload.Root accept="image/*" maxFiles={5} {transformFiles}>
<FileUpload.Label>File Upload with Compression</FileUpload.Label>
<FileUpload.Trigger>Choose Images</FileUpload.Trigger>
<FileUpload.ItemGroup>
<FileUpload.Context>
{#snippet render(fileUpload)}
{#each fileUpload().acceptedFiles as file (file.name)}
<FileUpload.Item {file} class="file-item">
<FileUpload.ItemPreview type="image/*">
<FileUpload.ItemPreviewImage />
</FileUpload.ItemPreview>
<FileUpload.ItemName />
<FileUpload.ItemSizeText />
<FileUpload.ItemDeleteTrigger>Remove</FileUpload.ItemDeleteTrigger>
</FileUpload.Item>
{/each}
{/snippet}
</FileUpload.Context>
</FileUpload.ItemGroup>
<FileUpload.HiddenInput />
</FileUpload.Root>
Custom Validation
Use the validate
prop to implement custom validation logic beyond the built-in constraints.
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>
<script lang="ts">
import { FileUpload } from '@ark-ui/svelte/file-upload'
import { FileIcon } from 'lucide-svelte'
function validateFile(file: File) {
if (file.name.length > 20) return ['FILE_NAME_TOO_LONG']
return null
}
</script>
<FileUpload.Root validate={validateFile}>
<FileUpload.Trigger>Choose file(s)</FileUpload.Trigger>
<FileUpload.ItemGroup>
<FileUpload.Context>
{#snippet render(context)}
{#each context().acceptedFiles as file (file.name)}
<FileUpload.Item {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>
{/each}
{/snippet}
</FileUpload.Context>
</FileUpload.ItemGroup>
<!-- Show rejected files -->
<FileUpload.Context>
{#snippet render(context)}
{#if context().rejectedFiles.length > 0}
<div style="color: red; margin-top: 1rem;">
<h4>Rejected Files:</h4>
{#each context().rejectedFiles as rejectedFile}
<p>{rejectedFile.file.name} - {rejectedFile.errors[0]}</p>
{/each}
</div>
{/if}
{/snippet}
</FileUpload.Context>
<FileUpload.HiddenInput />
</FileUpload.Root>
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, type FieldRootProps } from '@ark-ui/vue/field'
import { FileUpload } from '@ark-ui/vue/file-upload'
const props = defineProps<FieldRootProps>()
</script>
<template>
<Field.Root v-bind="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>
</template>
<script lang="ts">
import { Field } from '@ark-ui/svelte/field'
import { FileUpload } from '@ark-ui/svelte/file-upload'
</script>
<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>
Root Provider
Use the useFileUpload
hook to create the file upload store and pass it to the RootProvider
component. This allows
you to have maximum control over the file upload programmatically.
If you're using the
RootProvider
component, you don't need to use theRoot
component.
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>
<script lang="ts">
import { FileUpload, useFileUpload } from '@ark-ui/svelte/file-upload'
import { FileIcon } from 'lucide-svelte'
const id = $props.id()
const fileUpload = useFileUpload({ id, maxFiles: 5 })
</script>
<div>
<button onclick={() => fileUpload().clearFiles()}>Clear All Files</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>
{#snippet render(context)}
{#each context().acceptedFiles as file (file.name)}
<FileUpload.Item {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>
{/each}
{/snippet}
</FileUpload.Context>
</FileUpload.ItemGroup>
<FileUpload.HiddenInput />
</FileUpload.RootProvider>
</div>
Pasting Files
Use the setClipboardFiles
method to enable pasting images directly from the clipboard.
You can access the
fileUpload
store fromFileUpload.Context
as well.
import { FileUpload, useFileUpload } from '@ark-ui/react/file-upload'
import { XIcon } from 'lucide-react'
export const WithPaste = () => {
const fileUpload = useFileUpload({ maxFiles: 3, accept: 'image/*' })
return (
<FileUpload.RootProvider value={fileUpload}>
<FileUpload.Label>File Upload with Paste</FileUpload.Label>
<textarea
placeholder="Paste image here..."
onPaste={(e) => {
fileUpload.setClipboardFiles(e.clipboardData)
}}
/>
<FileUpload.ItemGroup>
{fileUpload.acceptedFiles.map((file) => (
<FileUpload.Item key={file.name} file={file}>
<FileUpload.ItemPreview type="image/*">
<FileUpload.ItemPreviewImage />
</FileUpload.ItemPreview>
<FileUpload.ItemDeleteTrigger>
<XIcon />
</FileUpload.ItemDeleteTrigger>
</FileUpload.Item>
))}
</FileUpload.ItemGroup>
<FileUpload.HiddenInput />
</FileUpload.RootProvider>
)
}
import { FileUpload, useFileUpload } from '@ark-ui/solid/file-upload'
import { For } from 'solid-js'
export const WithPaste = () => {
const fileUpload = useFileUpload({ maxFiles: 3, accept: 'image/*' })
return (
<FileUpload.RootProvider value={fileUpload}>
<FileUpload.Label>File Upload with Paste</FileUpload.Label>
<textarea
placeholder="Paste image here..."
onPaste={(e) => {
fileUpload().setClipboardFiles(e.clipboardData)
}}
/>
<FileUpload.ItemGroup>
<For each={fileUpload().acceptedFiles}>
{(file) => (
<FileUpload.Item file={file}>
<FileUpload.ItemPreview type="image/*">
<FileUpload.ItemPreviewImage />
</FileUpload.ItemPreview>
<FileUpload.ItemDeleteTrigger>X</FileUpload.ItemDeleteTrigger>
</FileUpload.Item>
)}
</For>
</FileUpload.ItemGroup>
<FileUpload.HiddenInput />
</FileUpload.RootProvider>
)
}
<script setup lang="ts">
import { FileUpload, useFileUpload } from '@ark-ui/vue/file-upload'
const fileUpload = useFileUpload({ maxFiles: 3, accept: 'image/*' })
</script>
<template>
<FileUpload.RootProvider :value="fileUpload">
<FileUpload.Label>File Upload with Paste</FileUpload.Label>
<textarea
placeholder="Paste image here..."
@paste="(e: ClipboardEvent) => fileUpload.setClipboardFiles(e.clipboardData)"
/>
<FileUpload.ItemGroup>
<FileUpload.Item v-for="file in fileUpload.acceptedFiles" :file="file" :key="file.name">
<FileUpload.ItemPreview type="image/*">
<FileUpload.ItemPreviewImage />
</FileUpload.ItemPreview>
<FileUpload.ItemDeleteTrigger>X</FileUpload.ItemDeleteTrigger>
</FileUpload.Item>
</FileUpload.ItemGroup>
<FileUpload.HiddenInput />
</FileUpload.RootProvider>
</template>
<script lang="ts">
import { FileUpload, useFileUpload } from '@ark-ui/svelte/file-upload'
const id = $props.id()
const fileUpload = useFileUpload({ id, maxFiles: 3, accept: 'image/*' })
</script>
<FileUpload.RootProvider value={fileUpload}>
<FileUpload.Label>File Upload with Paste</FileUpload.Label>
<textarea
placeholder="Paste image here..."
onpaste={(e) => fileUpload().setClipboardFiles(e.clipboardData)}
/>
<FileUpload.ItemGroup>
{#each fileUpload().acceptedFiles as file (file.name)}
<FileUpload.Item {file}>
<FileUpload.ItemPreview type="image/*">
<FileUpload.ItemPreviewImage />
</FileUpload.ItemPreview>
<FileUpload.ItemDeleteTrigger>X</FileUpload.ItemDeleteTrigger>
</FileUpload.Item>
{/each}
</FileUpload.ItemGroup>
<FileUpload.HiddenInput />
</FileUpload.RootProvider>
Guides
File Previews
The FileUpload
component provides flexible preview options for different file types. Use ItemPreview
with type
matching to show appropriate previews based on file format.
Preview Types:
type="image/*"
: Shows image thumbnails usingItemPreviewImage
type="video/*"
: For video file previewstype="application/pdf"
: For PDF filestype=".*"
: Generic fallback for any file type
<FileUpload.ItemPreview type="image/*">
<FileUpload.ItemPreviewImage />
</FileUpload.ItemPreview>
<FileUpload.ItemPreview type="video/*">
<VideoIcon />
</FileUpload.ItemPreview>
<FileUpload.ItemPreview type="application/pdf">
<PdfIcon />
</FileUpload.ItemPreview>
<FileUpload.ItemPreview type=".*">
<FileIcon />
</FileUpload.ItemPreview>
File Metadata Display:
ItemName
- Shows the file nameItemSizeText
- Shows formatted file size (e.g., "2.5 MB")
Disable dropzone
To disable drag-and-drop functionality, set the allowDrop
prop to false
.
<FileUpload.Root allowDrop={false}>{/* ... */}</FileUpload.Root>
Prevent document drop
By default, when the dropzone is active, we prevent accidental navigation when files are dropped outside the dropzone.
To prevent this behavior, set the preventDocumentDrop
prop to false
.
<FileUpload.Root preventDocumentDrop={false}>{/* ... */}</FileUpload.Root>
Prevent double open
When you want to delegate clicking to the trigger and remove the dropzone from the tab order, you can use the
disableClick
prop. This prevents the the file picker from opening twice.
<FileUpload.Dropzone disableClick>
<FileUpload.Trigger>Choose Files</FileUpload.Trigger>
Drag files here
</FileUpload.Dropzone>
API Reference
Root
Prop | Default | Type |
---|---|---|
accept | Record<string, string[]> | FileMimeType | FileMimeType[] The accept file types | |
acceptedFiles | File[] The controlled accepted files | |
allowDrop | true | boolean Whether to allow drag and drop in the dropzone element |
asChild | boolean Use 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 | |
defaultAcceptedFiles | File[] The default accepted files when rendered. Use when you don't need to control the accepted files of the input. | |
directory | boolean Whether to accept directories, only works in webkit browsers | |
disabled | boolean Whether 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 | boolean Whether the file input is invalid | |
locale | 'en-US' | string The current locale. Based on the BCP 47 definition. |
maxFiles | 1 | number The maximum number of files |
maxFileSize | Infinity | number The maximum file size in bytes |
minFileSize | 0 | number The minimum file size in bytes |
name | string The name of the underlying file input | |
onFileAccept | (details: FileAcceptDetails) => void Function called when the file is accepted | |
onFileChange | (details: FileChangeDetails) => void Function called when the value changes, whether accepted or rejected | |
onFileReject | (details: FileRejectDetails) => void Function called when the file is rejected | |
preventDocumentDrop | true | boolean Whether to prevent the drop event on the document |
required | boolean Whether the file input is required | |
transformFiles | (files: File[]) => Promise<File[]> Function to transform the accepted files to apply transformations | |
translations | IntlTranslations The localized messages to use. | |
validate | (file: File, details: FileValidateDetails) => FileError[] | null Function 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 | boolean Use 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 | boolean Use the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. | |
disableClick | boolean Whether 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 | boolean Use 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 | boolean Use 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 | boolean Use 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 | boolean Use 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 | boolean Use 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 | boolean Use the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. | |
type | '.*' | string The 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 | boolean Use 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 | boolean Use 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 | boolean Use 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 | boolean Use 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 | boolean Use 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 |