# UploadButton

```ts
import { UploadButton } from 'cx/widgets';
```

The `UploadButton` widget provides file selection and upload functionality. It displays a styled button that opens a file picker, then uploads selected files to a specified URL using XMLHttpRequest. The button shows upload progress and supports validation before upload.

```tsx
import { createModel } from "cx/data";
import { Controller } from "cx/ui";
import { UploadButton, MsgBox } from "cx/widgets";
import "../../icons/lucide";

interface Model {
  status: string;
}

const m = createModel<Model>();

class PageController extends Controller {
  onUploadStarting(xhr: XMLHttpRequest, instance: any, file: File): boolean {
    // Validate file type
    if (!file.type.startsWith("image/")) {
      MsgBox.alert("Only images are allowed.");
      return false;
    }
    // Validate file size (max 1MB)
    if (file.size > 1e6) {
      MsgBox.alert("File is too large (max 1MB).");
      return false;
    }
    this.store.set(m.status, `Uploading ${file.name}...`);
    return true;
  }

  onUploadProgress(event: ProgressEvent) {
    console.log(event);
    if (event.lengthComputable) {
      const percent = Math.round((event.loaded / event.total) * 100);
      this.store.set(m.status, `Uploading... ${percent}%`);
    }
  }

  onUploadComplete(xhr: XMLHttpRequest) {
    console.log("completed");
    this.store.set(m.status, `Upload complete (status: ${xhr.status})`);
  }

  onUploadError(error: any) {
    this.store.set(m.status, "Upload failed");
    console.error(error);
  }
}

export default (
  <div class="flex flex-col gap-4" controller={PageController}>
    <div class="flex flex-wrap gap-2">
      <UploadButton
        icon="upload"
        url="https://api.cxjs.io/uploads"
        accept="image/*"
        onUploadStarting={(xhr, instance, file) =>
          instance.getControllerByType(PageController).onUploadStarting(xhr, instance, file)
        }
        onUploadProgress={(event, instance) =>
          instance.getControllerByType(PageController).onUploadProgress(event)
        }
        onUploadComplete={(xhr, instance) =>
          instance.getControllerByType(PageController).onUploadComplete(xhr)
        }
        onUploadError={(error, instance) =>
          instance.getControllerByType(PageController).onUploadError(error)
        }
      >
        Upload Image
      </UploadButton>
      <UploadButton icon="upload" url="https://api.cxjs.io/uploads" disabled>
        Disabled
      </UploadButton>
    </div>
    <div class="text-sm text-gray-600" text={m.status} />
  </div>
);

```

## Dynamic URLs

For cloud uploads that require pre-signed URLs, use `onResolveUrl`:

```tsx
onResolveUrl={async (file, instance) => {
  const response = await fetch("/api/get-upload-url", {
    method: "POST",
    body: JSON.stringify({ filename: file.name }),
  });
  const { url } = await response.json();
  return url;
}}
```

## Configuration

### Core Properties

| Property               | Type      | Default                    | Description                                           |
| ---------------------- | --------- | -------------------------- | ----------------------------------------------------- |
| `url`                  | `string`  |                            | URL to upload files to                                |
| `text`                 | `string`  |                            | Text displayed on the button                          |
| `icon`                 | `string`  |                            | Icon name or configuration                            |
| `accept`               | `string`  |                            | File types accepted (e.g., `"image/*"`, `".pdf"`)     |
| `multiple`             | `boolean` | `false`                    | Allow multiple file selection                         |
| `method`               | `string`  | `"POST"`                   | HTTP method used for upload                           |
| `disabled`             | `boolean` | `false`                    | Disables the button                                   |
| `abortOnDestroy`       | `boolean` | `false`                    | Abort pending uploads when component is destroyed     |
| `uploadInProgressText` | `string`  | `"Upload is in progress."` | Validation message shown during upload                |

### Callbacks

| Property           | Type       | Description                                                                                   |
| ------------------ | ---------- | --------------------------------------------------------------------------------------------- |
| `onResolveUrl`     | `function` | `(file, instance) => string\|Promise<string>` - Resolve dynamic upload URL for each file     |
| `onUploadStarting` | `function` | `(xhr, instance, file, formData) => boolean\|Promise<boolean>` - Return `false` to cancel    |
| `onUploadComplete` | `function` | `(xhr, instance, file, formData) => void` - Called when upload completes                     |
| `onUploadProgress` | `function` | `(event, instance, file, formData) => void` - Called with progress updates                   |
| `onUploadError`    | `function` | `(error, instance, file, formData) => void` - Called when an error occurs                    |