PX-UI
Components

File Upload

A composable file upload solution with drag-and-drop, validation, previews, and optional upload progress.

Overview

The File Upload system gives you two ways to build file upload experiences: use FileUploadField for a ready-to-use component, or combine useFileUpload with the FileUpload.* primitives for fully custom UIs.

Import

// Recommended: ready-to-use component
import { FileUploadField } from "@px-ui/forms";

// Advanced: hook + composable primitives
import { FileUpload, useFileUpload } from "@px-ui/core";

// File type icons
import { getFileTypeIcon } from "@px-ui/core";

Usage

This example uses FileUploadField, which includes state management, validation, and optional upload support.

Paste Or Drag & Drop Files Here
Press Enter or Space to browse files, or drag and drop files here.
Paste Or Drag & Drop Files Here
import { FileUploadField } from "@px-ui/forms";

export function Uploader() {
  return (
    <FileUploadField
      accept="image/*,.pdf,.doc,.docx"
      maxSize={10 * 1024 * 1024}
      onFilesChange={(files) => console.log(files)}
    />
  );
}

Examples

With S3/presigned uploads

Configure uploads by providing upload.getPresignedUrl and upload.uploadFile. Progress is handled for you when uploadFile calls onProgress.

Paste Or Drag & Drop Files Here
Press Enter or Space to browse files, or drag and drop files here.
Paste Or Drag & Drop Files Here
import { FileUploadField } from "@px-ui/forms";

export function UploaderWithUpload() {
  return (
    <FileUploadField
      accept="image/*,.pdf"
      multiple
      maxFiles={5}
      maxSize={5 * 1024 * 1024}
      upload={{
        getPresignedUrl: async ({ filename, contentType, size }) => {
          const res = await fetch("/api/get-upload-url", {
            method: "POST",
            body: JSON.stringify({ filename, contentType, size }),
          });
          const data = await res.json();

          return {
            result: {
              url: data.url,
              fullPath: data.fullPath,
            },
          };
        },
        uploadFile: async (url, file, presignedData, onProgress) => {
          // Implement upload to `url` and call `onProgress(0..100)`.
          // Return the final URL (or rely on `presignedData.fullPath`).
          await fetch(url, { method: "PUT", body: file });

          onProgress?.(100);
          return { result: { url: presignedData.fullPath } };
        },
      }}
    />
  );
}

Fully custom UI (hook + primitives)

Use useFileUpload for state/actions and pass the entire hook return directly into FileUpload.Root.

Paste Or Drag & Drop Files Here
Press Enter or Space to browse files, or drag and drop files here.
Paste Or Drag & Drop Files Here
import { FileUpload, useFileUpload } from "@px-ui/core";

export function CustomDropzone() {
  const upload = useFileUpload({
    onFilesChange: (files) => console.log(files),
  });

  return (
    <FileUpload.Root upload={upload}>
      <FileUpload.Dropzone />
    </FileUpload.Root>
  );
}

Avatar upload (render prop)

Use the render prop for complete control over the dropzone element. The render function receives props (event handlers to spread) and state (current upload state).

Paste Or Drag & Drop Files Here
Press Enter or Space to browse files, or drag and drop files here.
Upload

Click or drag to upload avatar

import { FileUploadField } from "@px-ui/forms";

export function AvatarUpload() {
  return (
    <FileUploadField
      accept="image/*"
      maxSize={2 * 1024 * 1024}
      showFileList={false}
      render={(props, state) => (
        <div
          {...props}
          className={`flex size-32 cursor-pointer items-center justify-center rounded-full bg-neutral-100 transition-colors ${
            state.isDragging ? "border-2 border-dashed border-blue-500" : ""
          }`}
        >
          {state.files.length > 0 ? (
            <img
              src={state.files[0].preview}
              alt="Avatar"
              className="size-full rounded-full object-cover"
            />
          ) : (
            <div className="flex flex-col items-center justify-center text-neutral-500">
              <UserIcon className="size-10" />
              <span className="mt-1 text-xs">Upload</span>
            </div>
          )}
        </div>
      )}
    />
  );
}

Form reset integration

Use reset() and instanceKey for proper form reset handling.

Paste Or Drag & Drop Files Here
Press Enter or Space to browse files, or drag and drop files here.
Paste Or Drag & Drop Files Here
import { FileUpload, useFileUpload, Button } from "@px-ui/core";

export function FormResetDemo() {
  const [name, setName] = useState("");
  const upload = useFileUpload({
    multiple: true,
    upload: { getPresignedUrl, uploadFile },
    onFilesChange: (files) => console.log("Files changed:", files),
  });

  const handleSubmit = (e) => {
    e.preventDefault();
    console.log("Submitted:", { name, files: upload.files });
  };

  const handleReset = () => {
    setName("");
    upload.reset(); // Clears files, cancels uploads, increments instanceKey
  };

  return (
    <form onSubmit={handleSubmit}>
      <input value={name} onChange={(e) => setName(e.target.value)} />

      {/* Use instanceKey to force remount on reset */}
      <FileUpload.Root key={upload.instanceKey} upload={upload} multiple>
        <FileUpload.Dropzone size="sm" />
        <FileUpload.List>
          {(files) => files.map((file) => (
            <FileUpload.ListItem key={file.id} file={file} />
          ))}
        </FileUpload.List>
      </FileUpload.Root>

      <Button type="submit">Submit</Button>
      <Button type="button" onClick={handleReset}>Reset</Button>
    </form>
  );
}

File Type Icons

The library includes file type icons that automatically match based on file extension or MIME type.

import { getFileTypeIcon } from "@px-ui/core";

// Get the icon component for a file
const Icon = getFileTypeIcon({ name: "document.pdf", type: "application/pdf" });

// Use it like any React component
<Icon className="size-5" />

Available icons: PdfIcon, WordIcon, ExcelIcon, ImageIcon, VideoIcon, AudioIcon, ArchiveIcon, CodeIcon, TextIcon, GenericFileIcon.

Anatomy

The file upload primitives are designed to be composed together:

  • FileUpload.Root: Provides context for all child components (accepts upload prop)
  • FileUpload.Dropzone: Drag-and-drop area (also supports paste and keyboard)
  • FileUpload.List: Container for file items (render prop receives files)
  • FileUpload.ListItem: Consolidated file row with icon, name, size, progress, and actions
  • FileUpload.HiddenInput: Hidden file input (if you need it separately from Dropzone)
  • FileUpload.ClearAll: Clear all selected files button

API Reference

FileUploadField

A ready-to-use upload component that internally uses useFileUpload and the FileUpload.* primitives.

PropTypeDefaultDescription
size"sm" | "default" | "lg""default"Dropzone size
dropzoneTextstring"Paste Or Drag & Drop Files Here"Dropzone label text
buttonTextstring"Browse for files"Trigger button label
showFileListbooleantrueShow file list below the control
initialFilesArray<{ id: string; name: string; size: number; type: string; url: string }>[]Pre-populate with already-uploaded files
disabledbooleanfalseDisable all interactions
renderFileItem(file, actions) => React.ReactNode-Custom renderer for list items
onError(error: { type: string; message: string; files?: File[] }) => void-Validation errors callback

Also accepts hook options: accept, multiple, maxSize, maxFiles, onFilesChange, onFileAdd, onFileRemove, and upload.


useFileUpload

A hook that manages file selection, validation, previews, and (optionally) uploads.

Options:

OptionTypeDefaultDescription
acceptstring"*"Accepted file types (e.g. "image/*,.pdf")
multiplebooleanfalseAllow multiple files
maxSizenumberInfinityMax file size in bytes
maxFilesnumberInfinityMax file count (only when multiple)
initialFilesFileMetadata[][]Pre-populate with uploaded files
onFilesChange(files) => void-Called when files change
onFileAdd(file) => void-Called when a file is added
onFileRemove(file) => void-Called when a file is removed
uploadUploadConfig-Enables uploads and progress tracking

Returns: UseFileUploadReturn

  • State: files, isDragging, errors, isUploading
  • Core Actions: addFiles, removeFile, clearFiles, clearErrors
  • Form Integration: reset(newFiles?), setFiles(files | (prev) => files), instanceKey
  • Upload Actions: uploadFiles, retryUpload, cancelUpload
  • Drag/Drop + Input: handleDragEnter, handleDragLeave, handleDragOver, handleDrop, handleFileChange, openFileDialog, getInputProps

FileUpload.Root

Context provider - accepts the entire hook return value.

PropTypeRequiredDescription
uploadUseFileUploadReturnYesThe return value from useFileUpload()
acceptstringNoAccepted types (used by Dropzone/input)
multiplebooleanNoAllow multiple file selection
disabledbooleanNoDisable all interactions
classNamestringNoAdditional CSS classes

FileUpload.Dropzone

Drag-and-drop area (also supports paste, click, and keyboard Enter/Space).

PropTypeDefaultDescription
size"sm" | "default" | "lg""default"Dropzone size
dropzoneTextstring"Paste Or Drag & Drop Files Here"Primary label
browseTextstring"Browse for files"Button label
render(props: DropzoneRenderProps, state: DropzoneState) => ReactElement-Render prop for complete customization

Inherited Props: All native div props (when not using render).

DropzoneRenderProps

Props passed to your custom element (spread these on your element):

interface DropzoneRenderProps {
  onDragEnter: React.DragEventHandler;
  onDragLeave: React.DragEventHandler;
  onDragOver: React.DragEventHandler;
  onDrop: React.DragEventHandler;
  onPaste: React.ClipboardEventHandler;
  onKeyDown: React.KeyboardEventHandler;
  onClick: React.MouseEventHandler;
  tabIndex: number;
  role: string;
  "aria-disabled": boolean;
  "data-dragging": boolean;
}

DropzoneState

State passed to your render function for conditional styling:

interface DropzoneState {
  isDragging: boolean;
  isUploading: boolean;
  disabled: boolean;
  files: FileUploadItem[];
}

FileUpload.List

Renders nothing when there are no files.

PropTypeDefaultDescription
childrenReactNode | ((files) => ReactNode)-Render prop receives the current files

Inherited Props: All native div props.


FileUpload.ListItem

Consolidated file row component that replaces all the individual Item* parts.

PropTypeDefaultDescription
fileFileUploadItem-The file object (required)
showIconbooleantrueShow file type icon
showProgressbooleantrueShow progress bar when uploading
showErrorbooleantrueShow error message when status is error
showRemovebooleantrueShow remove button
showRetrybooleantrueShow retry button when status is error
iconReact.ReactNode-Custom icon (overrides auto-detected icon)

Inherited Props: All native div props.


FileUpload.HiddenInput

Hidden file input, useful if you need the input separate from the Dropzone.

Inherited Props: All native input props (except type).


FileUpload.ClearAll

Button to clear all selected files. Renders nothing when there are no files.

Inherited Props: All Button props.