File Upload
A comprehensive, headless file upload component with drag & drop, rich validation, and multi-state progress tracking.
Installation
npx raya-ui@latest add file-uploadFile Structure
your-project
components
ui
file-upload
FileUpload.vue
FileUploadDropzone.vue
FileUploadList.vue
FileUploadItem.vue
...
API Reference
FileUpload Props
v-modelFile[][]Array binding for the selected files.
acceptstringNative HTML accept attribute (e.g., "image/*").
max-filesnumberLimits the total number of selectable files.
on-validate(file: File) => string | nullCustom validation function. Return an error string to reject a file, or null to accept it.
on-uploadFunctionCallback that provides onProgress, onSuccess, and onError handlers for tracking async uploads.
Drag & drop files here
Or click to browse (max 2 files)
Settings
source-code.vue
<script setup lang="ts">
import { FileUpload, FileUploadDropzone, FileUploadTrigger, FileUploadList, FileUploadItem, FileUploadItemPreview, FileUploadItemMetadata, FileUploadItemDelete } from '@/components/ui/file-upload'
import { Button } from '@/components/ui/button'
import { Upload, X } from 'lucide-vue-next'
import { ref } from 'vue'
import { toast } from 'vue-sonner'
const files = ref<File[]>([])
const onValidate = (file: File) => {
if (files.value.length >= 2) return "Max 2 files"
if (!file.type.startsWith("image/")) return "Only images allowed"
if (file.size > 2 * 1024 * 1024) return "Max 2MB"
return null
}
const onReject = (file: File, msg: string) => toast.error(`${file.name}: ${msg}`)
</script>
<template>
<FileUpload
v-model="files"
accept="image/*"
:max-files="2"
:on-validate="onValidate"
:on-reject="onReject"
class="w-full max-w-md"
>
<FileUploadDropzone>
<div class="flex flex-col items-center gap-1">
<div class="flex items-center justify-center rounded-full border border-border p-2.5 bg-background">
<Upload class="size-6 text-muted-foreground" />
</div>
<p class="font-medium text-sm text-foreground">Drag & drop files here</p>
<p class="text-muted-foreground text-xs">Or click to browse (max 2 files)</p>
</div>
<FileUploadTrigger asChild>
<Button variant="outline" size="sm" class="mt-2 w-fit">Browse files</Button>
</FileUploadTrigger>
</FileUploadDropzone>
<FileUploadList>
<FileUploadItem v-for="file in files" :key="file.name" :file="file">
<FileUploadItemPreview />
<FileUploadItemMetadata />
<FileUploadItemDelete asChild>
<Button variant="ghost" size="icon" class="size-7"><X class="size-4" /></Button>
</FileUploadItemDelete>
</FileUploadItem>
</FileUploadList>
</FileUpload>
</template>