---
title: FileUpload
description: Drag-and-drop file upload component with progress tracking and file list management.
sidebar:
  order: 13
search:
  tags: [dropzone, files, upload, progress]
---

# FileUpload

> Drag-and-drop file upload component with progress tracking and file list management.

## Import

```tsx
import { FileUpload } from 'moraine'
```

## Slot Structure

Upload control and optional file preview list.

### Upload area

```text
root
└── control (dropzone or trigger button)
    └── wrapper
        ├── icon (Icon)
        ├── label (optional)
        └── description (optional)
```

### File preview list

```text
root
└── files
    └── file (×n)
        ├── filePreview
        ├── fileMeta
        │   ├── fileName
        │   └── fileSize
        └── fileRemove
```

## Examples

### Single Upload

Basic single-file mode with a live readout from onValueChange.

```tsx
function SingleUpload() {
  type FileUploadValue = FileUploadT.Value

  function fileNames(value: FileUploadValue): string {
    if (value === null) {
      return 'none'
    }

    if (Array.isArray(value)) {
      return value.length > 0 ? value.map((file) => file.name).join(', ') : 'none'
    }

    return value.name
  }

  const [singleValue, setSingleValue] = createSignal<FileUploadValue>(null)

  return (
    <div class="max-w-xl space-y-3">
      <FileUpload
        label="Upload one file"
        description="PNG, JPG, PDF up to your browser limit."
        accept="image/*,.pdf"
        onValueChange={setSingleValue}
      />
      <p class="text-xs text-muted-foreground">Selected file: {fileNames(singleValue())}</p>
    </div>
  )
}
```

### Multiple + Max Files

Append files across selections, reject overflow, and show selected names.

```tsx
function MultipleMaxFiles() {
  function fileCount(value: FileUploadValue): number {
    if (value === null) {
      return 0
    }

    if (Array.isArray(value)) {
      return value.length
    }

    return 1
  }

  function fileNames(value: FileUploadValue): string {
    if (value === null) {
      return 'none'
    }

    if (Array.isArray(value)) {
      return value.length > 0 ? value.map((file) => file.name).join(', ') : 'none'
    }

    return value.name
  }

  const [multiValue, setMultiValue] = createSignal<FileUploadValue>([])

  const [rejectedCount, setRejectedCount] = createSignal(0)

  type FileUploadValue = FileUploadT.Value

  return (
    <div class="max-w-xl space-y-3">
      <FileUpload
        multiple
        maxFiles={3}
        accept="image/*,.pdf"
        label="Upload up to 3 files"
        description="Drop or select multiple files."
        onValueChange={setMultiValue}
        onFileReject={(files) => setRejectedCount(files.length)}
      />
      <p class="text-xs text-muted-foreground">Selected count: {fileCount(multiValue())}</p>
      <p class="text-xs text-muted-foreground">Selected names: {fileNames(multiValue())}</p>
      <p class="text-xs text-muted-foreground">Last reject batch size: {rejectedCount()}</p>
    </div>
  )
}
```

### Sizes

Size scale from `xs` to `xl` for trigger height, spacing, and file list density.

```tsx
function Sizes() {
  const SIZES: FileUploadSize[] = ['xs', 'sm', 'md', 'lg', 'xl']

  return (
    <div class="max-w-xl space-y-3">
      <For each={SIZES}>
        {(size) => (
          <FileUpload
            size={size}
            dropzone={false}
            preview={false}
            label={`Upload (${size})`}
            description="Click to choose files."
          />
        )}
      </For>
    </div>
  )
}
```

### Trigger Mode (No Dropzone)

Use button-style trigger behavior by disabling dropzone interaction.

```tsx
function TriggerModeNoDropzone() {
  return (
    <div class="max-w-xl">
      <FileUpload
        dropzone={false}
        label="Select file"
        description="Click to choose files."
        preview={false}
      />
    </div>
  )
}
```

### Form Integration

Submit to validate a required attachment with Form + FormField.

```tsx
function FormIntegration() {
  const [formState, setFormState] = createSignal({
    attachment: null as File | null,
  })

  const updateFormAttachment = (value: FileUploadValue) => {
    const next = Array.isArray(value) ? (value[0] ?? null) : value
    setFormState((prev) => ({ ...prev, attachment: next }))
  }

  type FileUploadValue = FileUploadT.Value
  const form = createForm({
    schema: v.object({ attachment: v.file('Please upload one attachment.') }),
    initialInput: { attachment: undefined },
  })

  return (
    <Form of={form}>
      <div class="max-w-xl space-y-4">
        <FormField
          name="attachment"
          label="Attachment"
          description="Upload at least one file before submit."
          required
        >
          <FileUpload id="demo-attachment-upload" onValueChange={updateFormAttachment} />
        </FormField>
        <div class="flex gap-3 items-center">
          <Button type="submit" variant="secondary" size="sm">
            Validate
          </Button>
          <p class="text-xs text-muted-foreground">
            Current attachment: {formState().attachment?.name ?? 'none'}
          </p>
        </div>
      </div>
    </Form>
  )
}
```

## API Reference

### Attributes

#### `root`

Upload component container that owns dropzone, file input, and file list.

##### Data Attributes

| Attribute | Type | Description |
| --- | --- | --- |
| data-disabled | string \| undefined | Present when the component or item is disabled. |
| data-readonly | string \| undefined | Present when the field is read-only. |

##### ARIA Attributes

| Attribute | Type | Description |
| --- | --- | --- |
| role | string | Defines the semantic role exposed to assistive technology. |

#### `control`

Dropzone and picker control users interact with to select files.

##### Data Attributes

| Attribute | Type | Description |
| --- | --- | --- |
| data-dragging | string \| undefined | Present while the related thumb or handle is being dragged. |
| data-invalid | string \| undefined | Present when the field has a validation error. |

##### ARIA Attributes

| Attribute | Type | Description |
| --- | --- | --- |
| aria-disabled | boolean \| string \| undefined | Indicates that the control is disabled. |
| role | string | Defines the semantic role exposed to assistive technology. |

#### `wrapper`

Inner control layout for icon, label, and description.

#### `icon`

Upload or status icon shown inside the control.

#### `label`

Primary instruction text for the upload control.

#### `description`

Supporting upload requirements or helper text.

#### `files`

List region that displays selected files and upload progress.

#### `file`

Row for one selected file, including preview, metadata, and remove action.

#### `filePreview`

Preview or file-type icon area for a selected file.

#### `fileMeta`

Text region for file name, size, and validation state.

#### `fileName`

Display name for a selected file.

#### `fileSize`

File size text for a selected file.

#### `fileRemove`

Button used to remove a selected file from the list.

##### ARIA Attributes

| Attribute | Type | Description |
| --- | --- | --- |
| aria-label | boolean \| string \| undefined | Provides an accessible label when visible text is not sufficient. |

### Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| accept | string \| undefined | * | Accepted file types (e.g., ".jpg,.png", "image/*"). |
| as | "div" \| undefined | div | The HTML element or component to render as. |
| class | ClassValue | — | Class applied to the component root or trigger element. |
| classes | FileUploadT.Classes \| undefined | — | — |
| description | JSX.Element | — | Description text for the upload area. |
| disabled | boolean \| undefined | false | Whether the input is disabled. |
| dropzone | boolean \| undefined | true | Whether to enable drag and drop. |
| fileIcon | IconT.Name | icon-file | Icon to show for individual files when no preview is available. |
| icon | IconT.Name | icon-upload | Icon to show in the upload area. |
| id | string \| undefined | — | The ID of the input element. |
| label | JSX.Element | — | Label for the upload area. |
| maxFiles | number \| undefined | — | Maximum number of files allowed. |
| maxSize | number \| undefined | — | Maximum accepted file size in bytes. |
| minSize | number \| undefined | — | Minimum accepted file size in bytes. |
| multiple | boolean \| undefined | false | Whether multiple files can be uploaded. |
| name | string \| undefined | — | The name of the input element, used for form submission. |
| onClick | JSX.EventHandlerUnion<HTMLElement, MouseEvent> \| undefined | — | Click handler for the upload control. |
| onDragLeave | JSX.EventHandlerUnion<HTMLElement, DragEvent> \| undefined | — | Drag-leave handler for the upload dropzone. |
| onDragOver | JSX.EventHandlerUnion<HTMLElement, DragEvent> \| undefined | — | Drag-over handler for the upload dropzone. |
| onDrop | JSX.EventHandlerUnion<HTMLElement, DragEvent> \| undefined | — | Drop handler for the upload dropzone. |
| onFileReject | ((files: FileRejection[]) => void) \| undefined | — | Callback when files are rejected (e.g., due to type or count). |
| onKeyDown | JSX.EventHandlerUnion<HTMLElement, KeyboardEvent> \| undefined | — | Keyboard handler for the upload control. |
| onValueChange | ((value: FileUploadT.Value) => void) \| undefined | — | Callback when the selected files change. |
| preview | boolean \| undefined | true | Whether to show file previews. |
| readOnly | boolean \| undefined | false | Whether the input is read-only. |
| ref | JSX.HTMLElementTags["div"] extends { ref?: infer Ref; } ? Ref : never \| undefined | — | — |
| required | boolean \| undefined | false | Whether the input is required. |
| size | "xs" \| "sm" \| "md" \| "lg" \| "xl" \| undefined | — | — |
| style | JSX.CSSProperties \| undefined | — | — |
| styles | FileUploadT.Styles \| undefined | — | — |

### ARIA

Accessibility attributes and roles emitted by the component markup.

| Attribute | Type | Description |
| --- | --- | --- |
| aria-disabled | boolean \| string \| undefined | Indicates that the control is disabled. |
| aria-hidden | boolean \| string \| undefined | Hides decorative content from assistive technology. |
| aria-label | boolean \| string \| undefined | Provides an accessible label when visible text is not sufficient. |
| role | string | Defines the semantic role exposed to assistive technology. |

### Data Attributes

State and slot attributes exposed for styling hooks and selectors.

| Attribute | Type | Description |
| --- | --- | --- |
| data-disabled | string \| undefined | Present when the component or item is disabled. |
| data-dragging | string \| undefined | Present while the related thumb or handle is being dragged. |
| data-invalid | string \| undefined | Present when the field has a validation error. |
| data-readonly | string \| undefined | Present when the field is read-only. |
| data-slot | string | Identifies the rendered slot for styling hooks and selectors. |
