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 custom validation. Native file input attributes such as name and required are passed to the real input, and accept remains a native file picker hint. Handle upload, size checks, server errors, or Inertia form integration in the consuming application.
Basic Usage
Show code
<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.
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
<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
<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
<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
: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
: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);
}