Appearance
@vibe-labs/design-vue-uploads
Vue 3 file upload components for the Vibe Design System. Drop zone, file list with previews, upload queue with concurrency control, validation, auto-retry, and cancellation.
Installation
ts
import { VibeUploadZone, VibeUploadList, VibeUploadItem, VibeUploadIndicator, useUploadQueue } from "@vibe-labs/design-vue-uploads";Requires the CSS layer from @vibe-labs/design-components-uploads.
Quick Start
vue
<script setup>
import { VibeUploadZone, VibeUploadList, VibeUploadItem, useUploadQueue } from "@vibe-labs/design-vue-uploads";
const { files, addFiles, removeFile, retryFile, cancelFile, progress, isUploading } = useUploadQueue({
upload: async (file, { onProgress, signal }) => {
const form = new FormData();
form.append("file", file);
const res = await fetch("/api/upload", { method: "POST", body: form, signal });
return res.json(); // → stored as file.meta
},
concurrency: 3,
validation: {
accept: ["image/*", ".pdf"],
maxFileSize: 10 * 1024 * 1024, // 10MB
maxFiles: 20,
},
onValidationError: (errors) => toast.warning(errors[0].message),
});
</script>
<template>
<VibeUploadZone accept="image/*,.pdf" @files="addFiles">
<template #hint>Max 10MB per file · Images and PDFs</template>
</VibeUploadZone>
<VibeUploadList>
<VibeUploadItem v-for="f in files" :key="f.id" :file="f" @remove="removeFile" @retry="retryFile" @cancel="cancelFile" />
</VibeUploadList>
</template>Components
VibeUploadZone
Drop zone with drag-and-drop, click-to-browse, and directory upload support.
vue
<!-- Basic -->
<VibeUploadZone @files="addFiles" />
<!-- With accept filter -->
<VibeUploadZone accept="image/*,.pdf" @files="addFiles" />
<!-- Single file -->
<VibeUploadZone :multiple="false" @files="addFiles" />
<!-- Compact layout -->
<VibeUploadZone compact @files="addFiles" />
<!-- Directory upload -->
<VibeUploadZone directory @files="addFiles" />
<!-- Disabled -->
<VibeUploadZone disabled />
<!-- Custom content -->
<VibeUploadZone @files="addFiles" v-slot="{ isDragOver, open }">
<div :class="{ 'highlight': isDragOver }">
<p>Drop files here</p>
<button @click="open">Or click to browse</button>
</div>
</VibeUploadZone>
<!-- Custom label and hint -->
<VibeUploadZone @files="addFiles">
<template #label>Upload your documents</template>
<template #hint>PDF, DOC, up to 25MB</template>
</VibeUploadZone>Props
| Prop | Type | Default | Description |
|---|---|---|---|
accept | string | — | Accepted file types (HTML accept attribute) |
multiple | boolean | true | Allow multiple files |
disabled | boolean | false | Disabled state |
compact | boolean | false | Compact horizontal layout |
directory | boolean | false | Allow directory upload (webkitdirectory) |
Events
| Event | Payload | Description |
|---|---|---|
files | FileList | Files selected or dropped |
Slots
| Slot | Props | Description |
|---|---|---|
default | { isDragOver, open } | Full custom content |
icon | — | Custom upload icon |
label | — | Custom label text |
hint | — | Hint text below label |
Exposed
| Method | Description |
|---|---|
openFilePicker() | Programmatically open the file dialog |
VibeUploadList
Scrollable container for upload items.
vue
<VibeUploadList>
<VibeUploadItem v-for="f in files" :key="f.id" :file="f" />
</VibeUploadList>
<!-- Unbounded (no max-height) -->
<VibeUploadList unbounded>...</VibeUploadList>Props
| Prop | Type | Default | Description |
|---|---|---|---|
unbounded | boolean | false | Remove max-height constraint |
VibeUploadItem
Individual file row with thumbnail, progress bar, status icons, and actions.
vue
<VibeUploadItem :file="uploadFile" @remove="removeFile" @retry="retryFile" @cancel="cancelFile" />
<!-- Without preview -->
<VibeUploadItem :file="f" :show-preview="false" />
<!-- Non-removable -->
<VibeUploadItem :file="f" :removable="false" />
<!-- Custom thumbnail -->
<VibeUploadItem :file="f">
<template #thumb="{ file }">
<MyCustomThumb :file="file" />
</template>
</VibeUploadItem>Props
| Prop | Type | Default | Description |
|---|---|---|---|
file | UploadFile | required | The upload file object |
showPreview | boolean | true | Show thumbnail for images |
removable | boolean | true | Show remove/cancel button |
Events
| Event | Payload | Description |
|---|---|---|
remove | string (id) | Remove file |
retry | string (id) | Retry failed upload |
cancel | string (id) | Cancel in-progress upload |
Slots
| Slot | Props | Description |
|---|---|---|
thumb | { file } | Custom thumbnail |
file-icon | { file } | Icon for non-image files |
status | { status } | Custom status indicator |
VibeUploadIndicator
Compact status button showing upload progress — useful in toolbars or headers.
vue
<VibeUploadIndicator
:progress="progress"
:active="stats.uploading"
:total="stats.total"
:complete="isComplete"
:error="hasErrors"
@click="toggleUploadPanel"
/>
<!-- Custom label -->
<VibeUploadIndicator :progress="80" :active="2" :total="5" label="Uploading assets…" />Props
| Prop | Type | Default | Description |
|---|---|---|---|
progress | number | required | Overall progress 0–100 |
active | number | required | Active upload count |
total | number | required | Total file count |
complete | boolean | false | All complete |
error | boolean | false | Any errors |
label | string | auto | Label text override |
Composable
useUploadQueue(options)
The core upload engine — manages the queue, concurrency, validation, retry, and cancellation.
ts
const queue = useUploadQueue({
upload: async (file, { onProgress, signal }) => {
// Your upload implementation
// Call onProgress(0–100) for progress updates
// Respect signal for cancellation
return serverResponse; // → stored as file.meta
},
concurrency: 3,
autoUpload: true,
maxRetries: 2,
previews: true,
validation: {
accept: ["image/*", ".pdf", ".docx"],
maxFileSize: 10 * 1024 * 1024,
maxFiles: 20,
},
onValidationError: (errors) => {
/* handle invalid files */
},
onFileComplete: (file) => {
/* individual file done */
},
onFileError: (file) => {
/* individual file failed */
},
onQueueComplete: (files) => {
/* all files done */
},
});Options
| Option | Type | Default | Description |
|---|---|---|---|
upload | UploadFn<TMeta> | required | Upload implementation |
concurrency | number | 3 | Max parallel uploads |
autoUpload | boolean | true | Start immediately when files added |
maxRetries | number | 0 | Auto-retry count on failure |
previews | boolean | true | Generate image preview URLs |
validation | UploadValidation | — | Validation rules |
onValidationError | (errors) => void | — | Validation failure callback |
onFileComplete | (file) => void | — | Per-file completion |
onFileError | (file) => void | — | Per-file error |
onQueueComplete | (files) => void | — | All files done |
Returns
| Property | Type | Description |
|---|---|---|
files | Ref<UploadFile[]> | All files in the queue |
addFiles | (files) => void | Add files (validates first) |
removeFile | (id) => void | Remove file (cancels if active) |
retryFile | (id) => void | Retry a failed file |
cancelFile | (id) => void | Cancel active upload (aborts request) |
startAll | () => void | Start all queued (when autoUpload is false) |
cancelAll | () => void | Cancel all active uploads |
clearCompleted | () => void | Remove completed files |
clearAll | () => void | Remove all files, cancel active |
progress | ComputedRef<number> | Overall progress 0–100 |
isUploading | ComputedRef<boolean> | Any uploads active |
isComplete | ComputedRef<boolean> | All files done |
hasErrors | ComputedRef<boolean> | Any errors |
stats | ComputedRef<UploadStats> | Aggregate counts by status |
Upload File Lifecycle
queued → pending → uploading → complete
↘ error → (retry) → queued
↘ cancelled (via abort)Validation
Runs before files enter the queue. Three checks in order:
- Count — total files vs
maxFiles - Type — MIME wildcards (
image/*), exact MIME, or extensions (.pdf) - Size — file bytes vs
maxFileSize
Invalid files are reported via onValidationError and never enter the queue.
Dependencies
| Package | Purpose |
|---|---|
@vibe-labs/design-components-uploads | CSS tokens + generated styles |
Build
bash
npm run buildBuilt with Vite + vite-plugin-dts. Outputs ES module with TypeScript declarations.