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.

The file upload component doesn’t handle the actual file uploading process. It only handles the UI and the state of the file upload.

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.

    Return null if no validation errors are detected.

    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

    This behavior only works on mobile devices. On desktop devices, it will open the file system like normal.

    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(default: true)
    boolean
    Whether to allow drag and drop in the dropzone element
    maxFileSize(default: Infinity)
    number
    The maximum file size in bytes
    minFileSize(default: 0)
    number
    The minimum file size in bytes
    maxFiles(default: 1)
    number
    The maximum number of files
    preventDocumentDrop(default: true)
    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(default: "en-US")
    string
    The current locale. Based on the BCP 47 definition.
    dir(default: "ltr")
    "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. E.x.: Iframes, Electron.

    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

    attribute
    description
    data-scope
    file-upload
    data-part
    root
    data-disabled
    Present when disabled
    data-dragging
    Present when in the dragging state

    Dropzone

    attribute
    description
    data-scope
    file-upload
    data-part
    dropzone
    data-invalid
    Present when invalid
    data-disabled
    Present when disabled
    data-dragging
    Present when in the dragging state

    Trigger

    attribute
    description
    data-scope
    file-upload
    data-part
    trigger
    data-disabled
    Present when disabled
    data-invalid
    Present when invalid

    Item Group

    attribute
    description
    data-scope
    file-upload
    data-part
    item-group
    data-disabled
    Present when disabled

    Item

    attribute
    description
    data-scope
    file-upload
    data-part
    item
    data-disabled
    Present when disabled

    Item Name

    attribute
    description
    data-scope
    file-upload
    data-part
    item-name
    data-disabled
    Present when disabled

    Item Size Text

    attribute
    description
    data-scope
    file-upload
    data-part
    item-size-text
    data-disabled
    Present when disabled

    Item Preview

    attribute
    description
    data-scope
    file-upload
    data-part
    item-preview
    data-disabled
    Present when disabled

    Item Preview Image

    attribute
    description
    data-scope
    file-upload
    data-part
    item-preview-image
    data-disabled
    Present when disabled

    Item Delete Trigger

    attribute
    description
    data-scope
    file-upload
    data-part
    item-delete-trigger
    data-disabled
    Present when disabled

    Label

    attribute
    description
    data-scope
    file-upload
    data-part
    label
    data-disabled
    Present when disabled

    Clear Trigger

    attribute
    description
    data-scope
    file-upload
    data-part
    clear-trigger
    data-disabled
    Present when disabled