{"slug":"file-upload","title":"File Upload","description":"Using the file-upload machine in your project.","contentType":"component","framework":"react","content":"File upload component is used to upload multiple files.\n\nThe native input file element is quite difficult to style and doesn't provide a\ndrag-n-drop version.\n\n> The file upload component doesn't handle the actual file uploading process. It\n> only handles the UI and the state of the file upload.\n\n## Resources\n\n\n[Latest version: v1.31.0](https://www.npmjs.com/package/@zag-js/file-upload)\n[Logic Visualizer](https://zag-visualizer.vercel.app/file-upload)\n[Source Code](https://github.com/chakra-ui/zag/tree/main/packages/machines/file-upload)\n\n\n\n**Features**\n\n- Supports a button to open the file dialog\n- Supports drag and drop to upload files\n- Set the maximum number of files that can be uploaded\n- Set the maximum size of the files that can be uploaded\n- Set the accepted file types\n\n## Installation\n\nTo use the file upload machine in your project, run the following command in\nyour command line:\n\n```bash\nnpm install @zag-js/file-upload @zag-js/react\n# or\nyarn add @zag-js/file-upload @zag-js/react\n```\n\n## Anatomy\n\nTo set up the file upload correctly, you'll need to understand its anatomy and\nhow we name its parts.\n\n> Each part includes a `data-part` attribute to help identify them in the DOM.\n\n\n\n## Usage\n\nFirst, import the file upload package into your project\n\n```jsx\nimport * as fileUpload from \"@zag-js/file-upload\"\n```\n\nThe file upload package exports two key functions:\n\n- `machine` — The state machine logic for the file upload widget.\n- `connect` — The function that translates the machine's state to JSX attributes\n  and event handlers.\n\nNext, import the required hooks and functions for your framework and use the\nfile upload machine in your project 🔥\n\n```jsx\nimport * as fileUpload from \"@zag-js/file-upload\"\nimport { normalizeProps, useMachine } from \"@zag-js/react\"\nimport { useId } from \"react\"\n\nexport function FileUpload() {\n  const service = useMachine(fileUpload.machine, {\n    id: useId(),\n  })\n\n  const api = fileUpload.connect(service, normalizeProps)\n\n  return (\n    <div {...api.getRootProps()}>\n      <div {...api.getDropzoneProps()}>\n        <input {...api.getHiddenInputProps()} />\n        <span>Drag your file(s) here</span>\n      </div>\n\n      <button {...api.getTriggerProps()}>Choose file(s)</button>\n\n      <ul {...api.getItemGroupProps()}>\n        {api.acceptedFiles.map((file) => (\n          <li key={file.name} {...api.getItemProps({ file })}>\n            <div {...api.getItemNameProps({ file })}>{file.name}</div>\n            <button {...api.getItemDeleteTriggerProps({ file })}>Delete</button>\n          </li>\n        ))}\n      </ul>\n    </div>\n  )\n}\n```\n\n### Setting the accepted file types\n\nUse the `accept` attribute to set the accepted file types.\n\n```jsx\nconst service = useMachine(fileUpload.machine, {\n  accept: \"image/*\",\n})\n```\n\nAlternatively, you can provide an object with a MIME type and an array of file\nextensions.\n\n```jsx\nconst service = useMachine(fileUpload.machine, {\n  accept: {\n    \"image/png\": [\".png\"],\n    \"text/html\": [\".html\", \".htm\"],\n  },\n})\n```\n\n### Setting the maximum number of files\n\nUse the `maxFiles` attribute to set the maximum number of files that can be\nuploaded. This will set the `multiple` attribute on the underlying input\nelement.\n\n```jsx\nconst service = useMachine(fileUpload.machine, {\n  maxFiles: 5,\n})\n```\n\n### Setting the maximum size per file\n\nUse the `maxFileSize` attribute to set the maximum size per file that can be\nuploaded.\n\n```jsx\nconst service = useMachine(fileUpload.machine, {\n  maxFileSize: 1024 * 1024 * 10, // 10MB\n})\n```\n\n### Listening to file changes\n\nWhen files are uploaded, the `onFileChange` callback is invoked with the details\nof the accepted and rejected files.\n\n```jsx\nconst service = useMachine(fileUpload.machine, {\n  onFileChange(details) {\n    // details => { acceptedFiles: File[], rejectedFiles: { file: File, errors: [] }[] }\n    console.log(details.acceptedFiles)\n    console.log(details.rejectedFiles)\n  },\n})\n```\n\n### Usage within a form\n\nTo use the file upload within a form, set the `name` attribute in the machine's\ncontext, and ensure you render the input element `api.getHiddenInputProps()`\n\n```jsx\nconst service = useMachine(fileUpload.machine, {\n  name: \"avatar\",\n})\n```\n\n### Displaying image preview\n\nTo display a preview of the uploaded image, use the built-in FileReader API to\nread the file and set the `src` attribute of an image element.\n\n```jsx\nconst service = useMachine(fileUpload.machine, {\n  onFileChange(details) {\n    const reader = new FileReader()\n    reader.onload = (event) => {\n      const image = event.target.result\n      // set the image as the src of an image element\n    }\n    reader.readAsDataURL(details.acceptedFiles[0])\n  },\n})\n```\n\n### Applying custom validation\n\nTo apply custom validation, set the `validate` attribute to a function that\nreturns an **array of error strings**.\n\nThe returned array can contain any string as an error message. While zagjs\nsupports default errors such as `TOO_MANY_FILES`, `FILE_INVALID_TYPE`,\n`FILE_TOO_LARGE`, or `FILE_TOO_SMALL`, you can return any string that represents\nyour custom validation errors.\n\n> Return `null` if no validation errors are detected.\n\n```jsx\nconst service = useMachine(fileUpload.machine, {\n  validate(file) {\n    // Check if file size exceeds 10MB\n    if (file.size > 1024 * 1024 * 10) {\n      return [\"FILE_TOO_LARGE\"]\n    }\n    return null\n  },\n})\n```\n\nApply multiple validation errors:\n\n```js\nconst service = useMachine(fileUpload.machine, {\n  validate(file) {\n    const errors = []\n\n    // Check file size\n    if (file.size > 10 * 1024 * 1024) {\n      errors.push(\"FILE_TOO_LARGE\") // Default error enum\n    }\n\n    // Ensure file is a PDF\n    if (!file.name.endsWith(\".pdf\")) {\n      errors.push(\"ONLY_PDF_ALLOWED\") // Custom error\n    }\n\n    // Custom check: Reject duplicate files\n    const isDuplicate = details.acceptedFiles.some(\n      (acceptedFile) => acceptedFile.name === file.name,\n    )\n    if (isDuplicate) {\n      errors.push(\"FILE_EXISTS\")\n    }\n\n    return errors.length > 0 ? errors : null\n  },\n})\n```\n\n### Disabling drag and drop\n\nTo disable the drag and drop functionality, set the `allowDrop` context property\nto `false`.\n\n```jsx\nconst service = useMachine(fileUpload.machine, {\n  allowDrop: false,\n})\n```\n\n### Allowing directory selection\n\nSet the `directory` property to `true` to enable selecting directories instead\nof files.\n\nThis maps to the native input `webkitdirectory` HTML attribute and allows users\nto select directories and their contents.\n\n> Please note that support for this feature varies from browser to browser.\n\n```jsx\nconst service = useMachine(fileUpload.machine, {\n  directory: true,\n})\n```\n\n### Supporting media capture on mobile devices\n\nSet the `capture` property to specify the media capture mechanism to capture\nmedia on the spot. The value can be:\n\n- `user` for capturing media from the user-facing camera\n- `environment` for the outward-facing camera\n\n> This behavior only works on mobile devices. On desktop devices, it will open\n> the file system like normal.\n\n```jsx\nconst service = useMachine(fileUpload.machine, {\n  capture: \"user\",\n})\n```\n\n### Pasting files from clipboard\n\nAfter a user copies an image, to allow pasting the files from the clipboard, you\ncan listen for the paste event and use the `api.setFiles` method to set the\nfiles.\n\nHere's an example of how to do this in React.\n\n```jsx\nfunction Demo() {\n  const service = useMachine(fileUpload.machine, {\n    accept: \"image/*\",\n  })\n\n  const api = fileUpload.connect(service, normalizeProps)\n\n  return (\n    <textarea\n      onPaste={(event) => {\n        if (event.clipboardData?.files) {\n          api.setFiles(Array.from(event.clipboardData.files))\n        }\n      }}\n    />\n  )\n}\n```\n\n### Transforming files before acceptance\n\nUse the `transformFiles` callback to process files before they're added to\n`acceptedFiles`. This is useful for scenarios like image cropping, compression,\nor format conversion.\n\nThe `transformFiles` function receives the selected files and should return a\npromise that resolves with the transformed files.\n\n```jsx\nconst service = useMachine(fileUpload.machine, {\n  accept: \"image/*\",\n  transformFiles: async (files) => {\n    return Promise.all(\n      files.map(async (file) => {\n        // Compress or transform the file\n        const transformedBlob = await processImage(file)\n        return new File([transformedBlob], file.name, { type: file.type })\n      }),\n    )\n  },\n})\n```\n\nWhile files are being transformed, the `api.transforming` boolean is `true`,\nallowing you to show loading states in your UI.\n\n## Styling guide\n\nEarlier, we mentioned that each file upload part has a `data-part` attribute\nadded to them to select and style them in the DOM.\n\n```css\n[data-part=\"root\"] {\n  /* styles for root element*/\n}\n\n[data-part=\"dropzone\"] {\n  /* styles for root element*/\n}\n\n[data-part=\"trigger\"] {\n  /* styles for file picker trigger */\n}\n\n[data-part=\"label\"] {\n  /* styles for the input's label */\n}\n```\n\n### Dragging State\n\nWhen the user drags a file over the file upload, the `data-dragging` attribute\nis added to the `root` and `dropzone` parts.\n\n```css\n[data-part=\"root\"][data-dragging] {\n  /* styles for when the user is dragging a file over the file upload */\n}\n\n[data-part=\"dropzone\"][data-dragging] {\n  /* styles for when the user is dragging a file over the file upload */\n}\n```\n\n### Disabled State\n\nWhen the file upload is disabled, the `data-disabled` attribute is added to the\ncomponent parts.\n\n```css\n[data-part=\"root\"][data-disabled] {\n  /* styles for when the file upload is disabled */\n}\n\n[data-part=\"dropzone\"][data-disabled] {\n  /* styles for when the file upload is disabled */\n}\n\n[data-part=\"trigger\"][data-disabled] {\n  /* styles for when the file upload is disabled */\n}\n\n[data-part=\"label\"][data-disabled] {\n  /* styles for when the file upload is disabled */\n}\n```\n\n## Methods and Properties\n\n### Machine Context\n\nThe file upload machine exposes the following context properties:\n\n**`name`**\nType: `string`\nDescription: The name of the underlying file input\n\n**`ids`**\nType: `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; }>`\nDescription: The ids of the elements. Useful for composition.\n\n**`translations`**\nType: `IntlTranslations`\nDescription: The localized messages to use.\n\n**`accept`**\nType: `Record<string, string[]> | FileMimeType[]`\nDescription: The accept file types\n\n**`disabled`**\nType: `boolean`\nDescription: Whether the file input is disabled\n\n**`required`**\nType: `boolean`\nDescription: Whether the file input is required\n\n**`allowDrop`**\nType: `boolean`\nDescription: Whether to allow drag and drop in the dropzone element\n\n**`maxFileSize`**\nType: `number`\nDescription: The maximum file size in bytes\n\n**`minFileSize`**\nType: `number`\nDescription: The minimum file size in bytes\n\n**`maxFiles`**\nType: `number`\nDescription: The maximum number of files\n\n**`preventDocumentDrop`**\nType: `boolean`\nDescription: Whether to prevent the drop event on the document\n\n**`validate`**\nType: `(file: File, details: FileValidateDetails) => FileError[]`\nDescription: Function to validate a file\n\n**`defaultAcceptedFiles`**\nType: `File[]`\nDescription: The default accepted files when rendered.\nUse when you don't need to control the accepted files of the input.\n\n**`acceptedFiles`**\nType: `File[]`\nDescription: The controlled accepted files\n\n**`onFileChange`**\nType: `(details: FileChangeDetails) => void`\nDescription: Function called when the value changes, whether accepted or rejected\n\n**`onFileAccept`**\nType: `(details: FileAcceptDetails) => void`\nDescription: Function called when the file is accepted\n\n**`onFileReject`**\nType: `(details: FileRejectDetails) => void`\nDescription: Function called when the file is rejected\n\n**`capture`**\nType: `\"user\" | \"environment\"`\nDescription: The default camera to use when capturing media\n\n**`directory`**\nType: `boolean`\nDescription: Whether to accept directories, only works in webkit browsers\n\n**`invalid`**\nType: `boolean`\nDescription: Whether the file input is invalid\n\n**`transformFiles`**\nType: `(files: File[]) => Promise<File[]>`\nDescription: Function to transform the accepted files to apply transformations\n\n**`locale`**\nType: `string`\nDescription: The current locale. Based on the BCP 47 definition.\n\n**`dir`**\nType: `\"ltr\" | \"rtl\"`\nDescription: The document's text/writing direction.\n\n**`id`**\nType: `string`\nDescription: The unique identifier of the machine.\n\n**`getRootNode`**\nType: `() => ShadowRoot | Node | Document`\nDescription: A root node to correctly resolve document in custom environments. E.x.: Iframes, Electron.\n\n### Machine API\n\nThe file upload `api` exposes the following methods:\n\n**`dragging`**\nType: `boolean`\nDescription: Whether the user is dragging something over the root element\n\n**`focused`**\nType: `boolean`\nDescription: Whether the user is focused on the dropzone element\n\n**`disabled`**\nType: `boolean`\nDescription: Whether the file input is disabled\n\n**`transforming`**\nType: `boolean`\nDescription: Whether files are currently being transformed via `transformFiles`\n\n**`openFilePicker`**\nType: `VoidFunction`\nDescription: Function to open the file dialog\n\n**`deleteFile`**\nType: `(file: File, type?: ItemType) => void`\nDescription: Function to delete the file from the list\n\n**`acceptedFiles`**\nType: `File[]`\nDescription: The accepted files that have been dropped or selected\n\n**`rejectedFiles`**\nType: `FileRejection[]`\nDescription: The files that have been rejected\n\n**`setFiles`**\nType: `(files: File[]) => void`\nDescription: Sets the accepted files\n\n**`clearFiles`**\nType: `VoidFunction`\nDescription: Clears the accepted files\n\n**`clearRejectedFiles`**\nType: `VoidFunction`\nDescription: Clears the rejected files\n\n**`getFileSize`**\nType: `(file: File) => string`\nDescription: Returns the formatted file size (e.g. 1.2MB)\n\n**`createFileUrl`**\nType: `(file: File, cb: (url: string) => void) => VoidFunction`\nDescription: Returns the preview url of a file.\nReturns a function to revoke the url.\n\n**`setClipboardFiles`**\nType: `(dt: DataTransfer) => boolean`\nDescription: Sets the clipboard files\nReturns `true` if the clipboard data contains files, `false` otherwise.\n\n### Data Attributes\n\n**`Root`**\n\n**`data-scope`**: file-upload\n**`data-part`**: root\n**`data-disabled`**: Present when disabled\n**`data-dragging`**: Present when in the dragging state\n\n**`Dropzone`**\n\n**`data-scope`**: file-upload\n**`data-part`**: dropzone\n**`data-invalid`**: Present when invalid\n**`data-disabled`**: Present when disabled\n**`data-dragging`**: Present when in the dragging state\n\n**`Trigger`**\n\n**`data-scope`**: file-upload\n**`data-part`**: trigger\n**`data-disabled`**: Present when disabled\n**`data-invalid`**: Present when invalid\n\n**`ItemGroup`**\n\n**`data-scope`**: file-upload\n**`data-part`**: item-group\n**`data-disabled`**: Present when disabled\n**`data-type`**: The type of the item\n\n**`Item`**\n\n**`data-scope`**: file-upload\n**`data-part`**: item\n**`data-disabled`**: Present when disabled\n**`data-type`**: The type of the item\n\n**`ItemName`**\n\n**`data-scope`**: file-upload\n**`data-part`**: item-name\n**`data-disabled`**: Present when disabled\n**`data-type`**: The type of the item\n\n**`ItemSizeText`**\n\n**`data-scope`**: file-upload\n**`data-part`**: item-size-text\n**`data-disabled`**: Present when disabled\n**`data-type`**: The type of the item\n\n**`ItemPreview`**\n\n**`data-scope`**: file-upload\n**`data-part`**: item-preview\n**`data-disabled`**: Present when disabled\n**`data-type`**: The type of the item\n\n**`ItemPreviewImage`**\n\n**`data-scope`**: file-upload\n**`data-part`**: item-preview-image\n**`data-disabled`**: Present when disabled\n**`data-type`**: The type of the item\n\n**`ItemDeleteTrigger`**\n\n**`data-scope`**: file-upload\n**`data-part`**: item-delete-trigger\n**`data-disabled`**: Present when disabled\n**`data-type`**: The type of the item\n\n**`Label`**\n\n**`data-scope`**: file-upload\n**`data-part`**: label\n**`data-disabled`**: Present when disabled\n**`data-required`**: Present when required\n\n**`ClearTrigger`**\n\n**`data-scope`**: file-upload\n**`data-part`**: clear-trigger\n**`data-disabled`**: Present when disabled","package":"@zag-js/file-upload","editUrl":"https://github.com/chakra-ui/zag/edit/main/website/data/components/file-upload.mdx"}