Skip to content

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

PropTypeDefaultDescription
modelValueFile | File[] | nullnullSelected browser file or files.
existingFilesFileUploadExistingFile[][]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.
multiplebooleanfalseAllows selecting multiple files.
acceptstringundefinedNative file input accept hint. Dropped files are not filtered.
disabledbooleanfalseDisables file selection, drop handling, and remove actions.
showThumbnailsbooleanfalseShows small image thumbnails in the preview list.
labelstring'Drop your files here'Area variant title.
descriptionstring''Area variant description.
buttonLabelstring'Upload file'Button variant label.
buttonColorButtonBase color'primary'Button variant color.
iconIcon | nullnullReplaces the default upload icon.
rootClassstring''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

EventPayloadDescription
update:modelValueFile | File[] | nullEmitted when selected browser files change.
file-upload:addFile[]Emitted with the newly selected or dropped files.
file-upload:removeFileEmitted when a selected browser file is removed.
file-upload:remove-existingFileUploadExistingFileEmitted when an existing server-side file remove action is clicked.

Slots

SlotPropsDescription
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);
}