Skip to content
Destyler UI Destyler UI Destyler UI

File Upload

File upload component is used to upload multiple files.

The native input file element is quite difficult to style and doesn’t provide a drag-n-drop version.

Drag and drop files here

or

Drag and drop files here

or

    Drag and drop files here

    or

    Drag and drop files here

    or

      Features

      Install

      Install the component from your command line.

      Terminal window
            
              
      npm install @destyler/file-upload @destyler/vue
      Terminal window
            
              
      npm install @destyler/file-upload @destyler/react
      Terminal window
            
              
      npm install @destyler/file-upload @destyler/svelte
      Terminal window
            
              
      npm install @destyler/file-upload @destyler/solid

      Anatomy

      Import all parts and piece them together.

      <script setup lang="ts">
      import * as fileUpload from "@destyler/file-upload"
      import { normalizeProps, useMachine } from "@destyler/vue"
      import { computed,useId } from "vue"
      const [state, send] = useMachine(fileUpload.machine({ id: useId() }))
      const api = computed(() =>
      fileUpload.connect(state.value, send, normalizeProps),
      )
      </script>
      <template>
      <div v-bind="api.getRootProps()">
      <div v-bind="api.getDropzoneProps()">
      <input v-bind="api.getHiddenInputProps()" />
      <button v-bind="api.getTriggerProps()"></button>
      </div>
      <ul v-bind="api.getItemGroupProps()">
      <li
      v-for="file in api.acceptedFiles"
      :key="file.name"
      v-bind="api.getItemProps({ file })"
      >
      <div v-bind="api.getItemNameProps({ file })"></div>
      <button v-bind="api.getItemDeleteTriggerProps({ file })" ></button>
      </li>
      </ul>
      </div>
      </template>
      import * as fileUpload from '@destyler/file-upload'
      import { normalizeProps, useMachine } from '@destyler/react'
      import { useId } from 'react'
      export default function FileUpload() {
      const [state, send] = useMachine(fileUpload.machine({ id: useId() }))
      const api = fileUpload.connect(state, send, normalizeProps)
      return (
      <div {...api.getRootProps()}>
      <div {...api.getDropzoneProps()}>
      <input {...api.getHiddenInputProps()} />
      <button {...api.getTriggerProps()}> </button>
      </div>
      <ul
      {...api.getItemGroupProps()}
      >
      {api.acceptedFiles.map(file => (
      <li
      key={file.name}
      {...api.getItemProps({ file })}
      >
      <div {...api.getItemNameProps({ file })} ></div>
      <button {...api.getItemDeleteTriggerProps({ file })} ></button>
      </li>
      ))}
      </ul>
      </div>
      )
      }
      <script lang="ts">
      import * as fileUpload from "@destyler/file-upload"
      import { normalizeProps, useMachine } from "@destyler/svelte"
      const id = $props.id()
      const [state, send] = useMachine(fileUpload.machine({ id }))
      const api = $derived(fileUpload.connect(state, send, normalizeProps))
      </script>
      <div {...api.getRootProps()}>
      <div {...api.getDropzoneProps()}>
      <input {...api.getHiddenInputProps()} />
      <button {...api.getTriggerProps()} ></button>
      </div>
      <ul {...api.getItemGroupProps()}>
      {#each api.acceptedFiles as file (file.name)}
      <li
      {...api.getItemProps({ file })}
      >
      <div {...api.getItemNameProps({ file })} ></div>
      <button {...api.getItemDeleteTriggerProps({ file })} ></button>
      </li>
      {/each}
      </ul>
      </div>
      import * as fileUpload from '@destyler/file-upload'
      import { normalizeProps, useMachine } from '@destyler/solid'
      import { createMemo, createUniqueId, For } from 'solid-js'
      export default function FileUpload() {
      const [state, send] = useMachine(fileUpload.machine({ id: createUniqueId() }))
      const api = createMemo(() => fileUpload.connect(state, send, normalizeProps))
      return (
      <div {...api().getRootProps()}>
      <div {...api().getDropzoneProps()}>
      <input {...api().getHiddenInputProps()} />
      <button {...api().getTriggerProps()} ></button>
      </div>
      <ul
      {...api().getItemGroupProps()}
      >
      <For each={api().acceptedFiles}>
      {file => (
      <li
      {...api().getItemProps({ file })}
      >
      <div {...api().getItemNameProps({ file })}></div>
      <button {...api().getItemDeleteTriggerProps({ file })} ></button>
      </li>
      )}
      </For>
      </ul>
      </div>
      )
      }

      Setting the accepted file types

      Use the accept attribute to set the accepted file types.

      const [state, send] = useMachine(
      fileUpload.machine({
      accept: "image/*",
      }),
      )

      Alternatively, you can provide an object with a MIME type and an array of file extensions.

      const [state, send] = useMachine(
      fileUpload.machine({
      accept: {
      "image/png": [".png"],
      "text/html": [".html", ".htm"],
      },
      }),
      )

      Setting the maximum number of files

      Use the maxFiles attribute to set the maximum number of files that can be uploaded. This will set the multiple attribute on the underlying input element.

      const [state, send] = useMachine(
      fileUpload.machine({
      maxFiles: 5,
      }),
      )

      Setting the maximum size per file

      Use the maxFileSize attribute to set the maximum size per file that can be uploaded.

      const [state, send] = useMachine(
      fileUpload.machine({
      maxFileSize: 1024 * 1024 * 10, // 10MB
      }),
      )

      Listening to file changes

      When files are uploaded, the onFileChange callback is invoked with the details of the accepted and rejected files.

      const [state, send] = useMachine(
      fileUpload.machine({
      onFileChange(details) {
      // details => { acceptedFiles: File[], rejectedFiles: { file: File, errors: [] }[] }
      console.log(details.acceptedFiles)
      console.log(details.rejectedFiles)
      },
      }),
      )

      Usage within a form

      To use the file upload within a form, set the name attribute in the machine’s context, and ensure you render the input element api.getHiddenInputProps()

      const [state, send] = useMachine(
      fileUpload.machine({
      name: "word",
      }),
      )

      Displaying image preview

      To display a preview of the uploaded image, use the built-in FileReader API to read the file and set the src attribute of an image element.

      const [state, send] = useMachine(
      fileUpload.machine({
      onFileChange(details) {
      const reader = new FileReader()
      reader.onload = (event) => {
      const image = event.target.result
      // set the image as the src of an image element
      }
      reader.readAsDataURL(details.acceptedFiles[0])
      },
      }),
      )

      Applying custom validation

      To apply custom validation, set the validate attribute to a function that returns an array of error strings.

      The returned array can contain any string as an error message. While destyler supports default errors such as TOO_MANY_FILES, FILE_INVALID_TYPE, FILE_TOO_LARGE, or FILE_TOO_SMALL, you can return any string that represents your custom validation errors.

      const [state, send] = useMachine(
      fileUpload.machine({
      validate(file) {
      // Check if file size exceeds 10MB
      if (file.size > 1024 * 1024 * 10) {
      return ["FILE_TOO_LARGE"]
      }
      return null
      },
      }),
      )

      Apply multiple validation errors:

      const [state, send] = useMachine(
      fileUpload.machine({
      validate(file) {
      const errors = []
      // Check file size
      if (file.size > 10 * 1024 * 1024) {
      errors.push("FILE_TOO_LARGE") // Default error enum
      }
      // Ensure file is a PDF
      if (!file.name.endsWith(".pdf")) {
      errors.push("ONLY_PDF_ALLOWED") // Custom error
      }
      // Custom check: Reject duplicate files
      const isDuplicate = details.acceptedFiles.some(
      (acceptedFile) => acceptedFile.name === file.name,
      )
      if (isDuplicate) {
      errors.push("FILE_EXISTS")
      }
      return errors.length > 0 ? errors : null
      },
      }),
      )

      Disabling drag and drop

      To disable the drag and drop functionality, set the allowDrop context property to false.

      const [state, send] = useMachine(
      fileUpload.machine({
      allowDrop: false,
      }),
      )

      Allowing directory selection

      Set the directory property to true to enable selecting directories instead of files.

      This maps to the native input webkitdirectory HTML attribute and allows users to select directories and their contents.

      const [state, send] = useMachine(
      fileUpload.machine({
      directory: true,
      }),
      )

      Supporting media capture on mobile devices

      Set the capture property to specify the media capture mechanism to capture media on the spot. The value can be:

      • user for capturing media from the user-facing camera

      • environment for the outward-facing camera

      const [state, send] = useMachine(
      fileUpload.machine({
      capture: "user",
      }),
      )

      Styling guide

      Earlier, we mentioned that each file upload part has a data-part attribute added to them to select and style them in the DOM.

      [data-part="root"] {
      /* styles for root element*/
      }
      [data-part="dropzone"] {
      /* styles for root element*/
      }
      [data-part="trigger"] {
      /* styles for file picker trigger */
      }
      [data-part="label"] {
      /* styles for the input's label */
      }

      Dragging State

      When the user drags a file over the file upload, the data-dragging attribute is added to the root and dropzone parts.

      [data-part="root"][data-dragging] {
      /* styles for when the user is dragging a file over the file upload */
      }
      [data-part="dropzone"][data-dragging] {
      /* styles for when the user is dragging a file over the file upload */
      }

      Disabled State

      When the file upload is disabled, the data-disabled attribute is added to the component parts.

      [data-part="root"][data-disabled] {
      /* styles for when the file upload is disabled */
      }
      [data-part="dropzone"][data-disabled] {
      /* styles for when the file upload is disabled */
      }
      [data-part="trigger"][data-disabled] {
      /* styles for when the file upload is disabled */
      }
      [data-part="label"][data-disabled] {
      /* styles for when the file upload is disabled */
      }

      Methods and Properties

      Machine Context

      The file upload machine exposes the following context properties:

      name
      string
      The name of the underlying file input
      ids
      Partial<{ root: string; dropzone: string; hiddenInput: string; trigger: string; label: string; item(id: string): string; itemName(id: string): string; itemSizeText(id: string): string; itemPreview(id: string): string; }>
      The ids of the elements. Useful for composition.
      translations
      IntlTranslations
      The localized messages to use.
      accept
      Record<string, string[]> | FileMimeType[]
      The accept file types
      disabled
      boolean
      Whether the file input is disabled
      required
      boolean
      Whether the file input is required
      allowDrop
      boolean
      Whether to allow drag and drop in the dropzone element
      maxFileSize
      number
      The maximum file size in bytes
      minFileSize
      number
      The minimum file size in bytes
      maxFiles
      number
      The maximum number of files
      preventDocumentDrop
      boolean
      Whether to prevent the drop event on the document
      validate
      (file: File, details: FileValidateDetails) => FileError[]
      Function to validate a file
      onFileChange
      (details: FileChangeDetails) => void
      Function called when the value changes, whether accepted or rejected
      onFileAccept
      (details: FileAcceptDetails) => void
      Function called when the file is accepted
      onFileReject
      (details: FileRejectDetails) => void
      Function called when the file is rejected
      capture
      "user" | "environment"
      The default camera to use when capturing media
      directory
      boolean
      Whether to accept directories, only works in webkit browsers
      invalid
      boolean
      Whether the file input is invalid
      locale
      string
      The current locale. Based on the BCP 47 definition.
      dir
      "ltr" | "rtl"
      The document's text/writing direction.
      id
      string
      The unique identifier of the machine.
      getRootNode
      () => ShadowRoot | Node | Document
      A root node to correctly resolve document in custom environments.

      Machine API

      The file upload api exposes the following methods:

      dragging
      boolean
      Whether the user is dragging something over the root element
      focused
      boolean
      Whether the user is focused on the dropzone element
      disabled
      boolean
      Whether the file input is disabled
      openFilePicker
      () => void
      Function to open the file dialog
      deleteFile
      (file: File) => void
      Function to delete the file from the list
      acceptedFiles
      File[]
      The accepted files that have been dropped or selected
      rejectedFiles
      FileRejection[]
      The files that have been rejected
      setFiles
      (files: File[]) => void
      Function to set the value
      clearFiles
      () => void
      Function to clear the value
      clearRejectedFiles
      () => void
      Function to clear the rejected files
      getFileSize
      (file: File) => string
      Function to format the file size (e.g. 1.2MB)
      createFileUrl
      (file: File, cb: (url: string) => void) => VoidFunction
      Function to get the preview url of a file. Returns a function to revoke the url.
      setClipboardFiles
      (dt: DataTransfer) => boolean
      Function to set the clipboard files. Returns `true` if the clipboard data contains files, `false` otherwise.

      Data Attributes

      Root

      name
      desc
      data-disabled
      Present when disabled
      data-dragging
      Present when in the dragging state

      Dropzone

      name
      desc
      data-invalid
      Present when invalid
      data-disabled
      Present when disabled
      data-dragging
      Present when in the dragging state

      Trigger

      name
      desc
      data-disabled
      Present when disabled
      data-invalid
      Present when invalid

      Item Group

      name
      desc
      data-disabled
      Present when disabled

      Item

      name
      desc
      data-disabled
      Present when disabled

      Item Name

      name
      desc
      data-disabled
      Present when disabled

      Item Size Text

      name
      desc
      data-disabled
      Present when disabled

      Item Preview

      name
      desc
      data-disabled
      Present when disabled

      Item Preview Image

      name
      desc
      data-disabled
      Present when disabled

      Item Delete Trigger

      name
      desc
      data-disabled
      Present when disabled

      Label

      name
      desc
      data-disabled
      Present when disabled

      Clear Trigger

      name
      desc
      data-disabled
      Present when disabled