# File Upload

URL: https://ark-ui.com/docs/components/file-upload
LLM: https://ark-ui.com/llms.txt/components/file-upload

A component that is used to upload multiple files.

---



## Anatomy



```tsx
<FileUpload.Root>
  <FileUpload.Label />
  <FileUpload.Dropzone>
    <FileUpload.Trigger />
  </FileUpload.Dropzone>
  <FileUpload.ItemGroup>
    <FileUpload.Item>
      <FileUpload.ItemPreview>
        <FileUpload.ItemPreviewImage />
      </FileUpload.ItemPreview>
      <FileUpload.ItemName />
      <FileUpload.ItemSizeText />
      <FileUpload.ItemDeleteTrigger />
    </FileUpload.Item>
  </FileUpload.ItemGroup>
  <FileUpload.ClearTrigger />
  <FileUpload.HiddenInput />
</FileUpload.Root>
```

## Examples

```tsx
import { FileUpload } from '@ark-ui/react/file-upload'
import { PaperclipIcon, XIcon } from 'lucide-react'
import styles from 'styles/file-upload.module.css'

export const Basic = () => (
  <FileUpload.Root maxFiles={5} className={styles.Root}>
    <FileUpload.Label className={styles.Label}>File Upload</FileUpload.Label>
    <FileUpload.Trigger className={styles.Trigger}>
      <PaperclipIcon /> Choose file(s)
    </FileUpload.Trigger>
    <FileUpload.ItemGroup className={styles.ItemGroup}>
      <FileUpload.Context>
        {({ acceptedFiles }) =>
          acceptedFiles.map((file) => (
            <FileUpload.Item key={file.name} file={file} className={styles.ItemCompact}>
              <FileUpload.ItemName className={styles.ItemName} />
              <FileUpload.ItemDeleteTrigger className={styles.ItemDeleteTrigger}>
                <XIcon />
              </FileUpload.ItemDeleteTrigger>
            </FileUpload.Item>
          ))
        }
      </FileUpload.Context>
    </FileUpload.ItemGroup>
    <FileUpload.HiddenInput />
  </FileUpload.Root>
)
```

### Initial Files

Use the `defaultAcceptedFiles` prop to set the initial files in the file upload component.

```tsx
import { FileUpload } from '@ark-ui/react/file-upload'
import { FileIcon, PaperclipIcon, XIcon } from 'lucide-react'
import styles from 'styles/file-upload.module.css'

export const InitialFiles = () => (
  <FileUpload.Root
    defaultAcceptedFiles={[new File(['Welcome to Ark UI React'], 'README.md', { type: 'text/plain' })]}
    className={styles.Root}
  >
    <FileUpload.Label className={styles.Label}>File Upload</FileUpload.Label>
    <FileUpload.Trigger className={styles.Trigger}>
      <PaperclipIcon /> Choose file(s)
    </FileUpload.Trigger>
    <FileUpload.ItemGroup className={styles.ItemGroup}>
      <FileUpload.Context>
        {({ acceptedFiles }) =>
          acceptedFiles.map((file) => (
            <FileUpload.Item key={file.name} file={file} className={styles.Item}>
              <div className={styles.ItemPreview}>
                <FileIcon />
              </div>
              <FileUpload.ItemName className={styles.ItemName} />
              <FileUpload.ItemSizeText className={styles.ItemSizeText} />
              <FileUpload.ItemDeleteTrigger className={styles.ItemDeleteTrigger}>
                <XIcon />
              </FileUpload.ItemDeleteTrigger>
            </FileUpload.Item>
          ))
        }
      </FileUpload.Context>
    </FileUpload.ItemGroup>
    <FileUpload.HiddenInput />
  </FileUpload.Root>
)
```

### Clear Trigger

Use the `ClearTrigger` to remove all uploaded files at once.

```tsx
import { FileUpload } from '@ark-ui/react/file-upload'
import { PaperclipIcon, XIcon } from 'lucide-react'
import styles from 'styles/file-upload.module.css'

export const ClearTrigger = () => (
  <FileUpload.Root maxFiles={5} accept="image/png,image/jpeg" className={styles.Root}>
    <FileUpload.Label className={styles.Label}>File Upload</FileUpload.Label>
    <div className={styles.Actions}>
      <FileUpload.Trigger className={styles.Trigger}>
        <PaperclipIcon /> Choose file(s)
      </FileUpload.Trigger>
      <FileUpload.ClearTrigger className={styles.ClearTrigger}>Clear Files</FileUpload.ClearTrigger>
    </div>
    <FileUpload.ItemGroup className={styles.ItemGroup}>
      <FileUpload.Context>
        {({ acceptedFiles }) =>
          acceptedFiles.map((file) => (
            <FileUpload.Item key={file.name} file={file} className={styles.Item}>
              <FileUpload.ItemPreview type="image/*" className={styles.ItemPreview}>
                <FileUpload.ItemPreviewImage className={styles.ItemPreviewImage} />
              </FileUpload.ItemPreview>
              <FileUpload.ItemName className={styles.ItemName} />
              <FileUpload.ItemSizeText className={styles.ItemSizeText} />
              <FileUpload.ItemDeleteTrigger className={styles.ItemDeleteTrigger}>
                <XIcon />
              </FileUpload.ItemDeleteTrigger>
            </FileUpload.Item>
          ))
        }
      </FileUpload.Context>
    </FileUpload.ItemGroup>
    <FileUpload.HiddenInput />
  </FileUpload.Root>
)
```

### Dropzone

Use the `Dropzone` to enable drag-and-drop. It exposes a `data-dragging` attribute for styling.

```tsx
import { FileUpload } from '@ark-ui/react/file-upload'
import { FileIcon, UploadIcon, XIcon } from 'lucide-react'
import styles from 'styles/file-upload.module.css'

export const Dropzone = () => (
  <FileUpload.Root maxFiles={5} className={styles.Root}>
    <FileUpload.Label className={styles.Label}>File Upload</FileUpload.Label>
    <FileUpload.Dropzone className={styles.Dropzone}>
      <UploadIcon className={styles.DropzoneIcon} />
      <div className={styles.DropzoneContent}>
        <span className={styles.DropzoneTitle}>Drag and drop files here</span>
        <span className={styles.DropzoneDescription}>or click to browse</span>
      </div>
    </FileUpload.Dropzone>
    <FileUpload.ItemGroup className={styles.ItemGroup}>
      <FileUpload.Context>
        {({ acceptedFiles }) =>
          acceptedFiles.map((file) => (
            <FileUpload.Item key={file.name} file={file} className={styles.Item}>
              <FileUpload.ItemPreview type="image/*" className={styles.ItemPreview}>
                <FileUpload.ItemPreviewImage className={styles.ItemPreviewImage} />
              </FileUpload.ItemPreview>
              <FileUpload.ItemPreview type=".*" className={styles.ItemPreview}>
                <FileIcon />
              </FileUpload.ItemPreview>
              <FileUpload.ItemName className={styles.ItemName} />
              <FileUpload.ItemSizeText className={styles.ItemSizeText} />
              <FileUpload.ItemDeleteTrigger className={styles.ItemDeleteTrigger}>
                <XIcon />
              </FileUpload.ItemDeleteTrigger>
            </FileUpload.Item>
          ))
        }
      </FileUpload.Context>
    </FileUpload.ItemGroup>
    <FileUpload.HiddenInput />
  </FileUpload.Root>
)
```

### Directory Upload

Use the `directory` prop to upload entire folders. Access file paths through `file.webkitRelativePath`.

```tsx
import { FileUpload } from '@ark-ui/react/file-upload'
import { FileIcon, FolderIcon, XIcon } from 'lucide-react'
import styles from 'styles/file-upload.module.css'

export const DirectoryUpload = () => (
  <FileUpload.Root directory className={styles.Root}>
    <FileUpload.Label className={styles.Label}>Upload Folder</FileUpload.Label>
    <FileUpload.Trigger className={styles.Trigger}>
      <FolderIcon style={{ width: '1rem', height: '1rem' }} />
      Select Folder
    </FileUpload.Trigger>
    <FileUpload.ItemGroup className={styles.ItemGroup}>
      <FileUpload.Context>
        {({ acceptedFiles }) =>
          acceptedFiles.map((file) => (
            <FileUpload.Item key={file.name} file={file} className={styles.Item}>
              <div className={styles.ItemPreview}>
                <FileIcon />
              </div>
              <FileUpload.ItemName className={styles.ItemName}>
                {file.webkitRelativePath || file.name}
              </FileUpload.ItemName>
              <FileUpload.ItemSizeText className={styles.ItemSizeText} />
              <FileUpload.ItemDeleteTrigger className={styles.ItemDeleteTrigger}>
                <XIcon />
              </FileUpload.ItemDeleteTrigger>
            </FileUpload.Item>
          ))
        }
      </FileUpload.Context>
    </FileUpload.ItemGroup>
    <FileUpload.HiddenInput />
  </FileUpload.Root>
)
```

> When uploading directories with many files, set `maxFiles` to a higher value or remove it entirely to prevent
> rejections.

### Accepted File Types

Use the `accept` prop to restrict file types. Accepts MIME types (`image/png`) or extensions (`.pdf`).

```tsx
import { FileUpload } from '@ark-ui/react/file-upload'
import { AlertCircleIcon, ImageIcon, UploadIcon, XIcon } from 'lucide-react'
import styles from 'styles/file-upload.module.css'

export const AcceptedFileTypes = () => (
  <FileUpload.Root accept="image/png,image/jpeg" className={styles.Root}>
    <FileUpload.Label className={styles.Label}>Upload Images (PNG and JPEG only)</FileUpload.Label>
    <FileUpload.Dropzone className={styles.Dropzone}>
      <UploadIcon className={styles.DropzoneIcon} />
      <div className={styles.DropzoneContent}>
        <span className={styles.DropzoneTitle}>Drop your images here</span>
        <span className={styles.DropzoneDescription}>Only PNG and JPEG files</span>
      </div>
    </FileUpload.Dropzone>

    <FileUpload.Context>
      {({ acceptedFiles, rejectedFiles }) => (
        <>
          {acceptedFiles.length > 0 && (
            <FileUpload.ItemGroup className={styles.ItemGroup}>
              {acceptedFiles.map((file) => (
                <FileUpload.Item key={file.name} file={file} className={styles.Item}>
                  <FileUpload.ItemPreview type="image/*" className={styles.ItemPreview}>
                    <FileUpload.ItemPreviewImage className={styles.ItemPreviewImage} />
                  </FileUpload.ItemPreview>
                  <FileUpload.ItemPreview type=".*" className={styles.ItemPreview}>
                    <ImageIcon />
                  </FileUpload.ItemPreview>
                  <FileUpload.ItemName className={styles.ItemName} />
                  <FileUpload.ItemSizeText className={styles.ItemSizeText} />
                  <FileUpload.ItemDeleteTrigger className={styles.ItemDeleteTrigger}>
                    <XIcon />
                  </FileUpload.ItemDeleteTrigger>
                </FileUpload.Item>
              ))}
            </FileUpload.ItemGroup>
          )}

          {rejectedFiles.length > 0 && (
            <FileUpload.ItemGroup className={styles.ItemGroup}>
              {rejectedFiles.map((fileRejection) => (
                <FileUpload.Item
                  key={fileRejection.file.name}
                  file={fileRejection.file}
                  className={styles.Item}
                  data-rejected
                >
                  <div className={styles.ItemPreview}>
                    <AlertCircleIcon />
                  </div>
                  <FileUpload.ItemName className={styles.ItemName} />
                  <FileUpload.ItemSizeText className={styles.ItemSizeText} />
                  <div className={styles.ErrorList}>
                    {fileRejection.errors.map((error) => (
                      <div key={error} className={styles.ErrorItem}>
                        {error}
                      </div>
                    ))}
                  </div>
                </FileUpload.Item>
              ))}
            </FileUpload.ItemGroup>
          )}
        </>
      )}
    </FileUpload.Context>

    <FileUpload.HiddenInput />
  </FileUpload.Root>
)
```

### Error Handling

Set constraints with `maxFiles`, `maxFileSize`, `minFileSize`, and `accept`. Rejected files include error codes like
`TOO_MANY_FILES`, `FILE_INVALID_TYPE`, `FILE_TOO_LARGE`, or `FILE_EXISTS`.

```tsx
import { FileUpload, type FileUploadFileError } from '@ark-ui/react/file-upload'
import { AlertCircleIcon, CheckCircleIcon, FileIcon, UploadIcon, XIcon } from 'lucide-react'
import styles from 'styles/file-upload.module.css'

const errorMessages: Record<FileUploadFileError, string> = {
  TOO_MANY_FILES: 'Too many files selected (max 3 allowed)',
  FILE_INVALID_TYPE: 'Invalid file type (only images and PDFs allowed)',
  FILE_TOO_LARGE: 'File too large (max 1MB)',
  FILE_TOO_SMALL: 'File too small (min 1KB)',
  FILE_INVALID: 'Invalid file',
  FILE_EXISTS: 'File already exists',
}

export const ErrorHandling = () => (
  <FileUpload.Root
    maxFiles={3}
    maxFileSize={1024 * 1024}
    minFileSize={1024}
    accept="image/*,application/pdf"
    className={styles.Root}
  >
    <FileUpload.Label className={styles.Label}>Upload Documents</FileUpload.Label>
    <FileUpload.Dropzone className={styles.Dropzone}>
      <UploadIcon className={styles.DropzoneIcon} />
      <div className={styles.DropzoneContent}>
        <span className={styles.DropzoneTitle}>Drop files here</span>
        <span className={styles.DropzoneDescription}>Images and PDFs, max 1MB each</span>
      </div>
    </FileUpload.Dropzone>

    <FileUpload.Context>
      {({ acceptedFiles, rejectedFiles }) => (
        <>
          {acceptedFiles.length > 0 && (
            <div className={styles.Section}>
              <div className={styles.SectionTitle} data-status="accepted">
                <CheckCircleIcon
                  style={{
                    width: '0.875rem',
                    height: '0.875rem',
                    display: 'inline',
                    marginRight: '0.25rem',
                    verticalAlign: 'middle',
                  }}
                />
                Accepted Files
              </div>
              <FileUpload.ItemGroup className={styles.ItemGroup}>
                {acceptedFiles.map((file) => (
                  <FileUpload.Item key={file.name} file={file} className={styles.Item}>
                    <FileUpload.ItemPreview type="image/*" className={styles.ItemPreview}>
                      <FileUpload.ItemPreviewImage className={styles.ItemPreviewImage} />
                    </FileUpload.ItemPreview>
                    <FileUpload.ItemPreview type="application/pdf" className={styles.ItemPreview}>
                      <FileIcon />
                    </FileUpload.ItemPreview>
                    <FileUpload.ItemName className={styles.ItemName} />
                    <FileUpload.ItemSizeText className={styles.ItemSizeText} />
                    <FileUpload.ItemDeleteTrigger className={styles.ItemDeleteTrigger}>
                      <XIcon />
                    </FileUpload.ItemDeleteTrigger>
                  </FileUpload.Item>
                ))}
              </FileUpload.ItemGroup>
            </div>
          )}

          {rejectedFiles.length > 0 && (
            <div className={styles.Section}>
              <div className={styles.SectionTitle} data-status="rejected">
                <AlertCircleIcon
                  style={{
                    width: '0.875rem',
                    height: '0.875rem',
                    display: 'inline',
                    marginRight: '0.25rem',
                    verticalAlign: 'middle',
                  }}
                />
                Rejected Files
              </div>
              <FileUpload.ItemGroup className={styles.ItemGroup}>
                {rejectedFiles.map((fileRejection) => (
                  <FileUpload.Item
                    key={fileRejection.file.name}
                    file={fileRejection.file}
                    className={styles.Item}
                    data-rejected
                  >
                    <div className={styles.ItemPreview}>
                      <AlertCircleIcon />
                    </div>
                    <FileUpload.ItemName className={styles.ItemName} />
                    <FileUpload.ItemSizeText className={styles.ItemSizeText} />
                    <div className={styles.ErrorList}>
                      {fileRejection.errors.map((error, index) => (
                        <div key={index} className={styles.ErrorItem}>
                          {errorMessages[error as FileUploadFileError] || error}
                        </div>
                      ))}
                    </div>
                  </FileUpload.Item>
                ))}
              </FileUpload.ItemGroup>
            </div>
          )}
        </>
      )}
    </FileUpload.Context>

    <FileUpload.HiddenInput />
  </FileUpload.Root>
)
```

### File Transformations

Use `transformFiles` to process files before they're added. Useful for image compression, format conversion, or
resizing.

```tsx
import { FileUpload } from '@ark-ui/react/file-upload'
import { compressAccurately } from 'image-conversion'
import { ImageIcon, XIcon } from 'lucide-react'
import styles from 'styles/file-upload.module.css'

export const TransformFiles = () => {
  const transformFiles = async (files: File[]) => {
    return Promise.all(
      files.map(async (file) => {
        if (file.type.startsWith('image/')) {
          try {
            const blob = await compressAccurately(file, 200)
            return new File([blob], file.name, { type: blob.type })
          } catch (error) {
            console.error('Compression failed for:', file.name, error)
            return file
          }
        }
        return file
      }),
    )
  }

  return (
    <FileUpload.Root accept="image/*" maxFiles={5} transformFiles={transformFiles} className={styles.Root}>
      <FileUpload.Label className={styles.Label}>Upload with Compression</FileUpload.Label>
      <FileUpload.Trigger className={styles.Trigger}>
        <ImageIcon style={{ width: '1rem', height: '1rem' }} />
        Choose Images
      </FileUpload.Trigger>
      <FileUpload.ItemGroup className={styles.ItemGroup}>
        <FileUpload.Context>
          {({ acceptedFiles }) =>
            acceptedFiles.map((file) => (
              <FileUpload.Item key={file.name} file={file} className={styles.Item}>
                <FileUpload.ItemPreview type="image/*" className={styles.ItemPreview}>
                  <FileUpload.ItemPreviewImage className={styles.ItemPreviewImage} />
                </FileUpload.ItemPreview>
                <FileUpload.ItemName className={styles.ItemName} />
                <FileUpload.ItemSizeText className={styles.ItemSizeText} />
                <FileUpload.ItemDeleteTrigger className={styles.ItemDeleteTrigger}>
                  <XIcon />
                </FileUpload.ItemDeleteTrigger>
              </FileUpload.Item>
            ))
          }
        </FileUpload.Context>
      </FileUpload.ItemGroup>
      <FileUpload.HiddenInput />
    </FileUpload.Root>
  )
}
```

### Field

Use `Field` to add helper text and error handling.

```tsx
import { Field } from '@ark-ui/react/field'
import { FileUpload } from '@ark-ui/react/file-upload'
import { FileIcon, UploadIcon, XIcon } from 'lucide-react'
import field from 'styles/field.module.css'
import styles from 'styles/file-upload.module.css'

export const WithField = () => (
  <Field.Root className={field.Root}>
    <FileUpload.Root maxFiles={5} className={styles.Root}>
      <FileUpload.Label className={styles.Label}>Attachments</FileUpload.Label>
      <FileUpload.Dropzone className={styles.Dropzone}>
        <UploadIcon className={styles.DropzoneIcon} />
        <div className={styles.DropzoneContent}>
          <span className={styles.DropzoneTitle}>Drop files here</span>
          <span className={styles.DropzoneDescription}>or click to browse</span>
        </div>
      </FileUpload.Dropzone>
      <FileUpload.ItemGroup className={styles.ItemGroup}>
        <FileUpload.Context>
          {({ acceptedFiles }) =>
            acceptedFiles.map((file) => (
              <FileUpload.Item key={file.name} file={file} className={styles.Item}>
                <FileUpload.ItemPreview type="image/*" className={styles.ItemPreview}>
                  <FileUpload.ItemPreviewImage className={styles.ItemPreviewImage} />
                </FileUpload.ItemPreview>
                <FileUpload.ItemPreview type=".*" className={styles.ItemPreview}>
                  <FileIcon />
                </FileUpload.ItemPreview>
                <FileUpload.ItemName className={styles.ItemName} />
                <FileUpload.ItemSizeText className={styles.ItemSizeText} />
                <FileUpload.ItemDeleteTrigger className={styles.ItemDeleteTrigger}>
                  <XIcon />
                </FileUpload.ItemDeleteTrigger>
              </FileUpload.Item>
            ))
          }
        </FileUpload.Context>
      </FileUpload.ItemGroup>
      <FileUpload.HiddenInput data-testid="input" />
    </FileUpload.Root>
    <Field.HelperText className={field.HelperText}>Upload up to 5 files</Field.HelperText>
    <Field.ErrorText className={field.ErrorText}>Please upload at least one file</Field.ErrorText>
  </Field.Root>
)
```

### Root Provider

An alternative way to control the file upload is to use the `RootProvider` component and the `useFileUpload` hook. This
way you can access the state and methods from outside the component.

```tsx
import { FileUpload, useFileUpload } from '@ark-ui/react/file-upload'
import { FileIcon, UploadIcon, XIcon } from 'lucide-react'
import button from 'styles/button.module.css'
import styles from 'styles/file-upload.module.css'

export const RootProvider = () => {
  const fileUpload = useFileUpload({ maxFiles: 5 })

  return (
    <div className="stack">
      <button className={button.Root} onClick={() => fileUpload.clearFiles()}>
        Clear Files
      </button>
      <FileUpload.RootProvider value={fileUpload} className={styles.Root}>
        <FileUpload.Label className={styles.Label}>File Upload</FileUpload.Label>
        <FileUpload.Dropzone className={styles.Dropzone}>
          <UploadIcon className={styles.DropzoneIcon} />
          <div className={styles.DropzoneContent}>
            <span className={styles.DropzoneTitle}>Drop files here</span>
            <span className={styles.DropzoneDescription}>or click to browse</span>
          </div>
        </FileUpload.Dropzone>
        <FileUpload.ItemGroup className={styles.ItemGroup}>
          <FileUpload.Context>
            {({ acceptedFiles }) =>
              acceptedFiles.map((file) => (
                <FileUpload.Item key={file.name} file={file} className={styles.Item}>
                  <FileUpload.ItemPreview type="image/*" className={styles.ItemPreview}>
                    <FileUpload.ItemPreviewImage className={styles.ItemPreviewImage} />
                  </FileUpload.ItemPreview>
                  <FileUpload.ItemPreview type=".*" className={styles.ItemPreview}>
                    <FileIcon />
                  </FileUpload.ItemPreview>
                  <FileUpload.ItemName className={styles.ItemName} />
                  <FileUpload.ItemSizeText className={styles.ItemSizeText} />
                  <FileUpload.ItemDeleteTrigger className={styles.ItemDeleteTrigger}>
                    <XIcon />
                  </FileUpload.ItemDeleteTrigger>
                </FileUpload.Item>
              ))
            }
          </FileUpload.Context>
        </FileUpload.ItemGroup>
        <FileUpload.HiddenInput />
      </FileUpload.RootProvider>
    </div>
  )
}
```

### Pasting Files

Use `setClipboardFiles` to enable pasting images from the clipboard.

```tsx
import { FileUpload, useFileUpload } from '@ark-ui/react/file-upload'
import { ClipboardIcon, XIcon } from 'lucide-react'
import field from 'styles/field.module.css'
import styles from 'styles/file-upload.module.css'

export const PastingFiles = () => {
  const fileUpload = useFileUpload({ maxFiles: 3, accept: 'image/*' })

  return (
    <FileUpload.RootProvider value={fileUpload} className={styles.Root}>
      <FileUpload.Label className={styles.Label}>
        <ClipboardIcon
          style={{ width: '1rem', height: '1rem', display: 'inline', marginRight: '0.25rem', verticalAlign: 'middle' }}
        />
        Upload with Paste
      </FileUpload.Label>
      <textarea
        className={field.Textarea}
        placeholder="Paste an image here (Ctrl/Cmd + V)"
        onPaste={(e) => {
          fileUpload.setClipboardFiles(e.clipboardData)
        }}
      />
      <FileUpload.ItemGroup className={styles.ItemGroup}>
        {fileUpload.acceptedFiles.map((file) => (
          <FileUpload.Item key={file.name} file={file} className={styles.Item}>
            <FileUpload.ItemPreview type="image/*" className={styles.ItemPreview}>
              <FileUpload.ItemPreviewImage className={styles.ItemPreviewImage} />
            </FileUpload.ItemPreview>
            <FileUpload.ItemName className={styles.ItemName} />
            <FileUpload.ItemSizeText className={styles.ItemSizeText} />
            <FileUpload.ItemDeleteTrigger className={styles.ItemDeleteTrigger}>
              <XIcon />
            </FileUpload.ItemDeleteTrigger>
          </FileUpload.Item>
        ))}
      </FileUpload.ItemGroup>
      <FileUpload.HiddenInput />
    </FileUpload.RootProvider>
  )
}
```

### Media Capture

Use `capture` to access the device camera. Set to `"environment"` for back camera or `"user"` for front camera.

```tsx
import { FileUpload } from '@ark-ui/react/file-upload'
import { CameraIcon, FileIcon, XIcon } from 'lucide-react'
import styles from 'styles/file-upload.module.css'

export const MediaCapture = () => (
  <FileUpload.Root capture="environment" className={styles.Root}>
    <FileUpload.Label className={styles.Label}>Capture Photo</FileUpload.Label>
    <FileUpload.Trigger className={styles.Trigger}>
      <CameraIcon style={{ width: '1rem', height: '1rem' }} />
      Open Camera
    </FileUpload.Trigger>
    <FileUpload.ItemGroup className={styles.ItemGroup}>
      <FileUpload.Context>
        {({ acceptedFiles }) =>
          acceptedFiles.map((file) => (
            <FileUpload.Item key={file.name} file={file} className={styles.Item}>
              <FileUpload.ItemPreview type="image/*" className={styles.ItemPreview}>
                <FileUpload.ItemPreviewImage className={styles.ItemPreviewImage} />
              </FileUpload.ItemPreview>
              <FileUpload.ItemPreview type=".*" className={styles.ItemPreview}>
                <FileIcon />
              </FileUpload.ItemPreview>
              <FileUpload.ItemName className={styles.ItemName}>
                {file.webkitRelativePath || file.name}
              </FileUpload.ItemName>
              <FileUpload.ItemSizeText className={styles.ItemSizeText} />
              <FileUpload.ItemDeleteTrigger className={styles.ItemDeleteTrigger}>
                <XIcon />
              </FileUpload.ItemDeleteTrigger>
            </FileUpload.Item>
          ))
        }
      </FileUpload.Context>
    </FileUpload.ItemGroup>
    <FileUpload.HiddenInput />
  </FileUpload.Root>
)
```

### Rejected Files

Access `rejectedFiles` from the context to display validation errors.

```tsx
import { FileUpload } from '@ark-ui/react/file-upload'
import { AlertCircleIcon, CheckCircleIcon, UploadIcon, XIcon } from 'lucide-react'
import styles from 'styles/file-upload.module.css'

export const RejectedFiles = () => (
  <FileUpload.Root
    maxFiles={2}
    className={styles.Root}
    onFileReject={(details) => {
      console.log('Rejected files:', details)
    }}
  >
    <FileUpload.Label className={styles.Label}>Upload Files (Max 2)</FileUpload.Label>
    <FileUpload.Dropzone className={styles.Dropzone}>
      <UploadIcon className={styles.DropzoneIcon} />
      <div className={styles.DropzoneContent}>
        <span className={styles.DropzoneTitle}>Drop files here</span>
        <span className={styles.DropzoneDescription}>Maximum 2 files allowed</span>
      </div>
    </FileUpload.Dropzone>

    <FileUpload.Context>
      {({ acceptedFiles, rejectedFiles }) => (
        <>
          {acceptedFiles.length > 0 && (
            <div className={styles.Section}>
              <div className={styles.SectionTitle} data-status="accepted">
                <CheckCircleIcon
                  style={{
                    width: '0.875rem',
                    height: '0.875rem',
                    display: 'inline',
                    marginRight: '0.25rem',
                    verticalAlign: 'middle',
                  }}
                />
                Accepted Files
              </div>
              <FileUpload.ItemGroup type="accepted" className={styles.ItemGroup}>
                {acceptedFiles.map((file) => (
                  <FileUpload.Item key={file.name} file={file} className={styles.Item}>
                    <FileUpload.ItemPreview type="image/*" className={styles.ItemPreview}>
                      <FileUpload.ItemPreviewImage className={styles.ItemPreviewImage} />
                    </FileUpload.ItemPreview>
                    <FileUpload.ItemName className={styles.ItemName} />
                    <FileUpload.ItemSizeText className={styles.ItemSizeText} />
                    <FileUpload.ItemDeleteTrigger className={styles.ItemDeleteTrigger}>
                      <XIcon />
                    </FileUpload.ItemDeleteTrigger>
                  </FileUpload.Item>
                ))}
              </FileUpload.ItemGroup>
            </div>
          )}

          {rejectedFiles.length > 0 && (
            <div className={styles.Section}>
              <div className={styles.SectionTitle} data-status="rejected">
                <AlertCircleIcon
                  style={{
                    width: '0.875rem',
                    height: '0.875rem',
                    display: 'inline',
                    marginRight: '0.25rem',
                    verticalAlign: 'middle',
                  }}
                />
                Rejected Files
              </div>
              <FileUpload.ItemGroup type="rejected" className={styles.ItemGroup}>
                {rejectedFiles.map(({ file, errors }) => (
                  <FileUpload.Item key={file.name} file={file} className={styles.Item} data-rejected>
                    <div className={styles.ItemPreview}>
                      <AlertCircleIcon />
                    </div>
                    <FileUpload.ItemName className={styles.ItemName} />
                    <FileUpload.ItemSizeText className={styles.ItemSizeText} />
                    <div className={styles.ErrorList}>
                      {errors.map((error, index) => (
                        <span key={index} className={styles.ErrorItem}>
                          {error}
                        </span>
                      ))}
                    </div>
                  </FileUpload.Item>
                ))}
              </FileUpload.ItemGroup>
            </div>
          )}
        </>
      )}
    </FileUpload.Context>

    <FileUpload.HiddenInput />
  </FileUpload.Root>
)
```

## Guides

### File Previews

Use `ItemPreview` with type matching to show appropriate previews based on file format.

- `type="image/*"`: Shows image thumbnails using `ItemPreviewImage`
- `type="video/*"`: For video file previews
- `type="application/pdf"`: For PDF files
- `type=".*"`: Generic fallback for any file type

```tsx
<FileUpload.ItemPreview type="image/*">
  <FileUpload.ItemPreviewImage />
</FileUpload.ItemPreview>

<FileUpload.ItemPreview type="video/*">
  <VideoIcon />
</FileUpload.ItemPreview>

<FileUpload.ItemPreview type="application/pdf">
  <PdfIcon />
</FileUpload.ItemPreview>

<FileUpload.ItemPreview type=".*">
  <FileIcon />
</FileUpload.ItemPreview>
```

### Disable Dropzone

To disable drag-and-drop functionality, set `allowDrop` to `false`.

```tsx
<FileUpload.Root allowDrop={false}>{/* ... */}</FileUpload.Root>
```

### Prevent Document Drop

By default, we prevent accidental navigation when files are dropped outside the dropzone. Set `preventDocumentDrop` to
`false` to disable this.

```tsx
<FileUpload.Root preventDocumentDrop={false}>{/* ... */}</FileUpload.Root>
```

### Prevent Double Open

Use `disableClick` on `Dropzone` when delegating clicks to a nested `Trigger`. This prevents the file picker from
opening twice.

```tsx
<FileUpload.Dropzone disableClick>
  <FileUpload.Trigger>Choose Files</FileUpload.Trigger>
  Drag files here
</FileUpload.Dropzone>
```

## API Reference

### Props

### Root

#### Props

**`accept`**
Type: `Record<string, string[]> | FileMimeType | FileMimeType[]`
Required: false
Default Value: `undefined`
Description: The accept file types

**`acceptedFiles`**
Type: `File[]`
Required: false
Default Value: `undefined`
Description: The controlled accepted files

**`allowDrop`**
Type: `boolean`
Required: false
Default Value: `true`
Description: Whether to allow drag and drop in the dropzone element

**`asChild`**
Type: `boolean`
Required: false
Default Value: `undefined`
Description: Use the provided child element as the default rendered element, combining their props and behavior.

**`capture`**
Type: `'user' | 'environment'`
Required: false
Default Value: `undefined`
Description: The default camera to use when capturing media

**`defaultAcceptedFiles`**
Type: `File[]`
Required: false
Default Value: `undefined`
Description: The default accepted files when rendered.
Use when you don't need to control the accepted files of the input.

**`directory`**
Type: `boolean`
Required: false
Default Value: `undefined`
Description: Whether to accept directories, only works in webkit browsers

**`disabled`**
Type: `boolean`
Required: false
Default Value: `undefined`
Description: Whether the file input is disabled

**`ids`**
Type: `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
  itemDeleteTrigger: (id: string) => string
}>`
Required: false
Default Value: `undefined`
Description: The ids of the elements. Useful for composition.

**`invalid`**
Type: `boolean`
Required: false
Default Value: `undefined`
Description: Whether the file input is invalid

**`locale`**
Type: `string`
Required: false
Default Value: `"en-US"`
Description: The current locale. Based on the BCP 47 definition.

**`maxFiles`**
Type: `number`
Required: false
Default Value: `1`
Description: The maximum number of files

**`maxFileSize`**
Type: `number`
Required: false
Default Value: `Infinity`
Description: The maximum file size in bytes

**`minFileSize`**
Type: `number`
Required: false
Default Value: `0`
Description: The minimum file size in bytes

**`name`**
Type: `string`
Required: false
Default Value: `undefined`
Description: The name of the underlying file input

**`onFileAccept`**
Type: `(details: FileAcceptDetails) => void`
Required: false
Default Value: `undefined`
Description: Function called when the file is accepted

**`onFileChange`**
Type: `(details: FileChangeDetails) => void`
Required: false
Default Value: `undefined`
Description: Function called when the value changes, whether accepted or rejected

**`onFileReject`**
Type: `(details: FileRejectDetails) => void`
Required: false
Default Value: `undefined`
Description: Function called when the file is rejected

**`preventDocumentDrop`**
Type: `boolean`
Required: false
Default Value: `true`
Description: Whether to prevent the drop event on the document

**`readOnly`**
Type: `boolean`
Required: false
Default Value: `undefined`
Description: Whether the file input is read-only

**`required`**
Type: `boolean`
Required: false
Default Value: `undefined`
Description: Whether the file input is required

**`transformFiles`**
Type: `(files: File[]) => Promise<File[]>`
Required: false
Default Value: `undefined`
Description: Function to transform the accepted files to apply transformations

**`translations`**
Type: `IntlTranslations`
Required: false
Default Value: `undefined`
Description: The localized messages to use.

**`validate`**
Type: `(file: File, details: FileValidateDetails) => FileError[] | null`
Required: false
Default Value: `undefined`
Description: Function to validate a file

#### Data Attributes

**`data-scope`**: file-upload
**`data-part`**: root
**`data-disabled`**: Present when disabled
**`data-readonly`**: Present when read-only
**`data-dragging`**: Present when in the dragging state

### ClearTrigger

#### Props

**`asChild`**
Type: `boolean`
Required: false
Default Value: `undefined`
Description: Use the provided child element as the default rendered element, combining their props and behavior.

#### Data Attributes

**`data-scope`**: file-upload
**`data-part`**: clear-trigger
**`data-disabled`**: Present when disabled
**`data-readonly`**: Present when read-only

### Dropzone

#### Props

**`asChild`**
Type: `boolean`
Required: false
Default Value: `undefined`
Description: Use the provided child element as the default rendered element, combining their props and behavior.

**`disableClick`**
Type: `boolean`
Required: false
Default Value: `undefined`
Description: Whether to disable the click event on the dropzone

#### Data Attributes

**`data-scope`**: file-upload
**`data-part`**: dropzone
**`data-invalid`**: Present when invalid
**`data-disabled`**: Present when disabled
**`data-readonly`**: Present when read-only
**`data-dragging`**: Present when in the dragging state

### HiddenInput

#### Props

**`asChild`**
Type: `boolean`
Required: false
Default Value: `undefined`
Description: Use the provided child element as the default rendered element, combining their props and behavior.

### ItemDeleteTrigger

#### Props

**`asChild`**
Type: `boolean`
Required: false
Default Value: `undefined`
Description: Use the provided child element as the default rendered element, combining their props and behavior.

#### Data Attributes

**`data-scope`**: file-upload
**`data-part`**: item-delete-trigger
**`data-disabled`**: Present when disabled
**`data-readonly`**: Present when read-only
**`data-type`**: The type of the item

### ItemGroup

#### Props

**`asChild`**
Type: `boolean`
Required: false
Default Value: `undefined`
Description: Use the provided child element as the default rendered element, combining their props and behavior.

**`type`**
Type: `ItemType`
Required: false
Default Value: `undefined`
Description: undefined

#### Data Attributes

**`data-scope`**: file-upload
**`data-part`**: item-group
**`data-disabled`**: Present when disabled
**`data-type`**: The type of the item

### ItemName

#### Props

**`asChild`**
Type: `boolean`
Required: false
Default Value: `undefined`
Description: Use the provided child element as the default rendered element, combining their props and behavior.

#### Data Attributes

**`data-scope`**: file-upload
**`data-part`**: item-name
**`data-disabled`**: Present when disabled
**`data-type`**: The type of the item

### ItemPreviewImage

#### Props

**`asChild`**
Type: `boolean`
Required: false
Default Value: `undefined`
Description: Use the provided child element as the default rendered element, combining their props and behavior.

#### Data Attributes

**`data-scope`**: file-upload
**`data-part`**: item-preview-image
**`data-disabled`**: Present when disabled
**`data-type`**: The type of the item

### ItemPreview

#### Props

**`asChild`**
Type: `boolean`
Required: false
Default Value: `undefined`
Description: Use the provided child element as the default rendered element, combining their props and behavior.

**`type`**
Type: `string`
Required: false
Default Value: `'.*'`
Description: The file type to match against. Matches all file types by default.

#### Data Attributes

**`data-scope`**: file-upload
**`data-part`**: item-preview
**`data-disabled`**: Present when disabled
**`data-type`**: The type of the item

### Item

#### Props

**`file`**
Type: `File`
Required: true
Default Value: `undefined`
Description: undefined

**`asChild`**
Type: `boolean`
Required: false
Default Value: `undefined`
Description: Use the provided child element as the default rendered element, combining their props and behavior.

#### Data Attributes

**`data-scope`**: file-upload
**`data-part`**: item
**`data-disabled`**: Present when disabled
**`data-type`**: The type of the item

### ItemSizeText

#### Props

**`asChild`**
Type: `boolean`
Required: false
Default Value: `undefined`
Description: Use the provided child element as the default rendered element, combining their props and behavior.

#### Data Attributes

**`data-scope`**: file-upload
**`data-part`**: item-size-text
**`data-disabled`**: Present when disabled
**`data-type`**: The type of the item

### Label

#### Props

**`asChild`**
Type: `boolean`
Required: false
Default Value: `undefined`
Description: Use the provided child element as the default rendered element, combining their props and behavior.

#### Data Attributes

**`data-scope`**: file-upload
**`data-part`**: label
**`data-disabled`**: Present when disabled
**`data-required`**: Present when required

### RootProvider

#### Props

**`value`**
Type: `UseFileUploadReturn`
Required: true
Default Value: `undefined`
Description: undefined

**`asChild`**
Type: `boolean`
Required: false
Default Value: `undefined`
Description: Use the provided child element as the default rendered element, combining their props and behavior.

### Trigger

#### Props

**`asChild`**
Type: `boolean`
Required: false
Default Value: `undefined`
Description: Use the provided child element as the default rendered element, combining their props and behavior.

#### Data Attributes

**`data-scope`**: file-upload
**`data-part`**: trigger
**`data-disabled`**: Present when disabled
**`data-readonly`**: Present when read-only
**`data-invalid`**: Present when invalid

### Context

**API:**

| Property | Type | Description |
|----------|------|-------------|
| `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 |
| `readOnly` | `boolean` | Whether the file input is in read-only mode |
| `transforming` | `boolean` | Whether files are currently being transformed via `transformFiles` |
| `maxFilesReached` | `boolean` | Whether the maximum number of files has been reached |
| `remainingFiles` | `number` | The number of files that can still be added |
| `openFilePicker` | `VoidFunction` | Function to open the file dialog |
| `deleteFile` | `(file: File, type?: ItemType | undefined) => 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` | Sets the accepted files |
| `clearFiles` | `VoidFunction` | Clears the accepted files |
| `clearRejectedFiles` | `VoidFunction` | Clears the rejected files |
| `getFileSize` | `(file: File) => string` | Returns the formatted file size (e.g. 1.2MB) |
| `createFileUrl` | `(file: File, cb: (url: string) => void) => VoidFunction` | Returns the preview url of a file.
Returns a function to revoke the url. |
| `setClipboardFiles` | `(dt: DataTransfer | null) => boolean` | Sets the clipboard files
Returns `true` if the clipboard data contains files, `false` otherwise. |
