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.
Features
- Supports a button to open the file dialog.
- Supports drag and drop to upload files.
- Set the maximum number of files that can be uploaded.
- Set the maximum size of the files that can be uploaded.
- Set the accepted file types.
Installation
To use the file upload machine in your project, run the following command in your command line:
npm install @zag-js/file-upload @zag-js/react # or yarn add @zag-js/file-upload @zag-js/react
npm install @zag-js/file-upload @zag-js/vue # or yarn add @zag-js/file-upload @zag-js/vue
npm install @zag-js/file-upload @zag-js/vue # or yarn add @zag-js/file-upload @zag-js/vue
npm install @zag-js/file-upload @zag-js/solid # or yarn add @zag-js/file-upload @zag-js/solid
This command will install the framework agnostic file upload logic and the reactive utilities for your framework of choice.
Anatomy
To set up the file upload correctly, you'll need to understand its anatomy and how we name its parts.
Each part includes a
data-part
attribute to help identify them in the DOM.
On a high level, the file upload consists of:
- Root: The root element of the file upload.
- Dropzone: The element that handles the drag and drop functionality.
- Input: The visually hidden input element.
- Trigger: The element that triggers the file upload.
- Delete Trigger: The element that triggers the deletion of a file.
Usage
First, import the file upload package into your project
import * as fileUpload from "@zag-js/file-upload"
The file upload package exports two key functions:
machine
— The state machine logic for the file upload widget.connect
— The function that translates the machine's state to JSX attributes and event handlers.
Next, import the required hooks and functions for your framework and use the file uplaod machine in your project 🔥
import * as fileUpload from "@zag-js/file-upload" import { normalizeProps, useMachine } from "@zag-js/react" import { useId } from "react" export function FileUpload() { const [state, send] = useMachine( fileUpload.machine({ id: useId(), }), ) const api = fileUpload.connect(state, send, normalizeProps) return ( <div {...api.rootProps}> <div {...api.dropzoneProps}> <input {...api.hiddenInputProps} /> <span>Drag your file(s) here</span> </div> <button {...api.triggerProps}>Choose file(s)</button> <ul> {api.files.map((file) => ( <li key={file.name}> <div>{file.name}</div> <button {...api.getDeleteTriggerProps({ file })}>Delete</button> </li> ))} </ul> </div> ) }
import * as fileUpload from "@zag-js/file-upload" import { normalizeProps, useMachine } from "@zag-js/vue" import { computed, defineComponent } from "vue" export const FileUpload = defineComponent({ name: "FileUpload", setup() { const [state, send] = useMachine(fileUpload.machine({ id: "1" }), { context: controls.context, }) const apiRef = computed(() => fileUpload.connect(state.value, send, normalizeProps), ) return () => { const api = apiRef.value return ( <div {...api.rootProps}> <div {...api.dropzoneProps}> <input {...api.hiddenInputProps} /> Drag your files here </div> <button {...api.triggerProps}>Choose Files...</button> <ul> {api.files.map((file) => { return ( <li key={file.name}> <div>{file.name}</div> <button {...api.getDeleteTriggerProps({ file })}> Delete </button> </li> ) })} </ul> </div> ) } }, })
<script setup> import * as fileUpload from "@zag-js/file-upload" import { normalizeProps, useMachine } from "@zag-js/vue" import { computed } from "vue" const [state, send] = useMachine(fileUpload.machine({ id: "1" })) const api = computed(() => fileUpload.connect(state.value, send, normalizeProps), ) </script> <template> <div v-bind="api.rootProps"> <div v-bind="api.dropzoneProps"> <input v-bind="api.hiddenInputProps" /> Drag your files here </div> <button v-bind="api.triggerProps">Choose Files...</button> <ul> <li v-for="file in api.files" :key="{file.name}"> <div>{{ file.name }}</div> <button v-bind="api.getDeleteTriggerProps({ file })" }>Delete</button> </li> </ul> </div> </template>
import * as fileUpload from "@zag-js/file-upload" import { normalizeProps, useMachine } from "@zag-js/solid" import { createUniqueId, createMemo } from "solid-js" export function FileUpload() { const [state, send] = useMachine( fileUpload.machine({ id: createUniqueId(), }), ) const api = createMemo(() => fileUpload.connect(state, send, normalizeProps)) return ( <div {...api.rootProps}> <div {...api().dropzoneProps}> <input {...api().hiddenInputProps} /> <span>Drag your file(s) here</span> </div> <button {...api().triggerProps}>Choose file(s)</button> <ul> <For each={api().files}> {(file) => ( <li> <div>{file.name}</div> <button {...api().getDeleteTriggerProps({ file })}>Delete</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/*", }), )
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 onFilesChange
callback is invoked with the
details of the accepted and rejected files.
const [state, send] = useMachine( fileUpload.machine({ onFilesChange: (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.hiddenInputProps
const [state, send] = useMachine( fileUpload.machine({ name: "avatar", }), )
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({ onFilesChange: (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]) }, }), )
Disabling drag and drop
To disable the drag and drop functionalty, set the allowDrop
context property
to false
.
const [state, send] = useMachine( fileUpload.machine({ allowDrop: false, }), )
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 element.
[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
root element.
[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 trigger is disabled */ } [data-part="label"] [data-disabled] { /* styles for when the file upload label is disabled */ }
Methods and Properties
The file upload's api
method exposes the following methods:
isDragging
boolean
Whether the user is dragging something over the root elementisFocused
boolean
Whether the user is focused on the root elementopen
() => void
Function to open the file dialogdeleteFile
(file: File) => void
Function to delete the file from the listfiles
File[]
The files that have been dropped or selectedsetValue
(files: File[]) => void
Function to set the valueclearValue
() => void
Function to clear the value
Edit this page on GitHub