FileUpload Component
The FileUpload component provides a pure Vue file input with an area variant, a ButtonBase trigger variant, drag-and-drop support, selected file previews, existing file previews, and optional image thumbnails.
It does not perform validation. Use accept as a native file picker hint and handle upload, size checks, server errors, or Inertia form integration in the consuming application.
Basic Usage
Show code
vue
<template>
<FileUpload
v-model="imageFile"
accept="image/*"
label="Drop your image here"
description="Upload SVG, PNG, JPG or GIF"
show-thumbnails
/>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { FileUpload } from '@robuust-digital/vue-components/core';
const imageFile = ref<File | null>(null);
</script>Props
| Prop | Type | Default | Description |
|---|---|---|---|
modelValue | File | File[] | null | null | Selected browser file or files. |
existingFiles | FileUploadExistingFile[] | [] | Server-side files rendered in the preview list. |
variant | 'area' | 'button' | 'area' | Renders either a drag/drop area or a ButtonBase trigger. |
size | 'base' | 'sm' | 'base' | Controls area and button sizing. |
multiple | boolean | false | Allows selecting multiple files. |
accept | string | undefined | Native file input accept hint. Dropped files are not filtered. |
disabled | boolean | false | Disables file selection, drop handling, and remove actions. |
showThumbnails | boolean | false | Shows small image thumbnails in the preview list. |
label | string | 'Drop your files here' | Area variant title. |
description | string | '' | Area variant description. |
buttonLabel | string | 'Upload file' | Button variant label. |
buttonColor | ButtonBase color | 'primary' | Button variant color. |
icon | Icon | null | null | Replaces the default upload icon. |
rootClass | string | '' | Adds classes to the root element. |
Existing Files
Existing files are rendered in the preview list, but they are not part of v-model. Removing an existing file emits file-upload:remove-existing so the consuming app can update state or call the backend.
ts
type FileUploadExistingFile = {
id: string | number;
name: string;
url?: string;
thumbnailUrl?: string;
size?: number;
type?: string;
};Events
| Event | Payload | Description |
|---|---|---|
update:modelValue | File | File[] | null | Emitted when selected browser files change. |
file-upload:add | File[] | Emitted with the newly selected or dropped files. |
file-upload:remove | File | Emitted when a selected browser file is removed. |
file-upload:remove-existing | FileUploadExistingFile | Emitted when an existing server-side file remove action is clicked. |
Slots
| Slot | Props | Description |
|---|---|---|
icon | { icon } | Replaces the default upload icon rendering. |
preview | { files, existingFiles, removeFile, removeExistingFile } | Replaces the preview list while keeping remove callbacks available. |
Examples
Multiple Files
Show code
vue
<template>
<FileUpload
v-model="documentFiles"
multiple
label="Drop your files here"
description="Select or drop multiple files"
/>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { FileUpload } from '@robuust-digital/vue-components/core';
const documentFiles = ref<File[]>([]);
</script>Button Variant With Existing Files
Show code
vue
<template>
<FileUpload
v-model="contractFiles"
:existing-files="existingFiles"
variant="button"
button-label="Upload contract"
button-color="secondary"
show-thumbnails
@file-upload:remove-existing="removeExistingFile"
/>
</template>Custom Preview Slot
Show code
vue
<template>
<FileUpload
v-model="customPreviewFiles"
multiple
>
<template #preview="{ files, existingFiles, removeFile, removeExistingFile }">
<div class="flex flex-col gap-2">
<button
v-for="file in files"
:key="file.name"
type="button"
@click="removeFile(file)"
>
Remove {{ file.name }}
</button>
<button
v-for="file in existingFiles"
:key="file.id"
type="button"
@click="removeExistingFile(file)"
>
Remove {{ file.name }}
</button>
</div>
</template>
</FileUpload>
</template>CSS Customization ⚡️
Use CSS variables to adapt the upload area and preview list without overriding component internals.
Common overrides
css
:root {
--rvc-file-upload-area-border-style: dashed;
--rvc-file-upload-area-border-color: var(--rvc-base-border-color);
--rvc-file-upload-area-border-radius: var(--rvc-base-border-radius);
--rvc-file-upload-area-bg-color: var(--color-white);
--rvc-file-upload-area-bg-color-dragging: var(--color-indigo-50);
--rvc-file-upload-icon-size: calc(var(--spacing) * 7);
--rvc-file-upload-list-gap: calc(var(--spacing) * 1.5);
--rvc-file-upload-item-padding-x: calc(var(--spacing) * 2.5);
--rvc-file-upload-item-padding-y: calc(var(--spacing) * 1.5);
--rvc-file-upload-thumbnail-size: calc(var(--spacing) * 8);
--rvc-file-upload-thumbnail-border-radius: calc(infinity * 1px);
}Available variables
Show all FileUpload CSS variables
css
:root {
/* Root */
--rvc-file-upload-gap: calc(var(--spacing) * 3);
--rvc-file-upload-transition-duration: var(--rvc-base-transition-duration);
--rvc-file-upload-transition-timing-function: var(--rvc-base-transition-timing-function);
--rvc-file-upload-transition-property: color, background-color, border-color;
--rvc-file-upload-disabled-opacity: var(--rvc-base-input-disabled-opacity);
/* Area */
--rvc-file-upload-area-border-radius: var(--rvc-base-border-radius);
--rvc-file-upload-area-border-width: var(--rvc-base-border-width);
--rvc-file-upload-area-border-style: dashed;
--rvc-file-upload-area-border-color: var(--rvc-base-border-color);
--rvc-file-upload-area-border-color-hover: var(--rvc-base-border-dark-color);
--rvc-file-upload-area-border-color-dragging: var(--color-indigo-500);
--rvc-file-upload-area-bg-color: var(--color-white);
--rvc-file-upload-area-bg-color-hover: var(--color-slate-50);
--rvc-file-upload-area-bg-color-dragging: var(--color-indigo-50);
--rvc-file-upload-area-padding-x: calc(var(--spacing) * 6);
--rvc-file-upload-area-padding-y: calc(var(--spacing) * 8);
--rvc-file-upload-area-min-height: calc(var(--spacing) * 40);
--rvc-file-upload-area-gap: calc(var(--spacing) * 1);
/* Icon and text */
--rvc-file-upload-icon-size: calc(var(--spacing) * 7);
--rvc-file-upload-icon-color: var(--color-slate-400);
--rvc-file-upload-icon-color-hover: var(--color-slate-500);
--rvc-file-upload-label-color: var(--color-slate-900);
--rvc-file-upload-label-font-size: var(--text-base);
--rvc-file-upload-label-font-weight: var(--font-weight-medium);
--rvc-file-upload-description-color: var(--color-slate-500);
--rvc-file-upload-description-font-size: var(--text-sm);
/* Preview list */
--rvc-file-upload-list-gap: calc(var(--spacing) * 1.5);
--rvc-file-upload-item-gap: calc(var(--spacing) * 2);
--rvc-file-upload-item-padding-x: calc(var(--spacing) * 2.5);
--rvc-file-upload-item-padding-y: calc(var(--spacing) * 1.5);
--rvc-file-upload-item-border-radius: var(--rvc-base-border-radius);
--rvc-file-upload-item-border-width: var(--rvc-base-border-width);
--rvc-file-upload-item-border-style: solid;
--rvc-file-upload-item-border-color: var(--rvc-base-border-color);
--rvc-file-upload-item-bg-color: var(--color-white);
--rvc-file-upload-file-color: var(--color-slate-700);
--rvc-file-upload-file-font-size: var(--text-sm);
/* Thumbnail */
--rvc-file-upload-thumbnail-size: calc(var(--spacing) * 8);
--rvc-file-upload-thumbnail-border-radius: calc(infinity * 1px);
--rvc-file-upload-thumbnail-bg-color: var(--color-slate-100);
--rvc-file-upload-thumbnail-object-fit: cover;
/* Remove action */
--rvc-file-upload-remove-size: calc(var(--spacing) * 6);
--rvc-file-upload-remove-border-radius: var(--rvc-base-border-radius);
--rvc-file-upload-remove-color: var(--color-slate-400);
--rvc-file-upload-remove-color-hover: var(--color-red-600);
--rvc-file-upload-remove-bg-color-hover: var(--color-red-50);
--rvc-file-upload-remove-icon-size: calc(var(--spacing) * 4);
/* Small variant */
--rvc-file-upload-gap-sm: calc(var(--spacing) * 2);
--rvc-file-upload-area-padding-x-sm: calc(var(--spacing) * 4);
--rvc-file-upload-area-padding-y-sm: calc(var(--spacing) * 4);
--rvc-file-upload-area-min-height-sm: calc(var(--spacing) * 24);
--rvc-file-upload-icon-size-sm: calc(var(--spacing) * 5);
}