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";

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 } };
        },
      }}
    />

);
}

Variants (dropzone, button, compact)

FileUploadField supports three layout variants.

Button Variant

Compact Variant

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

// Dropzone (default)
<FileUploadField variant="dropzone" />

// Button
<FileUploadField variant="button" buttonText="Upload Files" multiple />

// Compact
<FileUploadField variant="compact" accept=".pdf,.doc,.docx" />

Image uploads with a grid

For image-first workflows, render a grid instead of a list.

Drop images here
Press Enter or Space to browse files, or drag and drop files here.
Drop images here
import { FileUploadField } from "@px-ui/forms";

<FileUploadField
  accept="image/*"
  multiple
  maxFiles={8}
  showImageGrid
  size="sm"
  dropzoneText="Drop images here"
  buttonText="Select images"
/>

Fully custom UI (hook + primitives)

Use useFileUpload for state/actions and pass them into FileUpload.Root. Everything else is composed with FileUpload.*.

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 [state, actions] = useFileUpload({
    onFilesChange: (files) => console.log(files),
  });

return (

<FileUpload.Root
  files={state.files}
  addFiles={actions.addFiles}
  removeFile={actions.removeFile}
  clearFiles={actions.clearFiles}
  openFileDialog={actions.openFileDialog}
  getInputProps={actions.getInputProps}
  handleDragEnter={actions.handleDragEnter}
  handleDragLeave={actions.handleDragLeave}
  handleDragOver={actions.handleDragOver}
  handleDrop={actions.handleDrop}
  isDragActive={state.isDragging}
>
  <FileUpload.Dropzone />
</FileUpload.Root>
); }

List view for mixed file types

A list layout is useful for documents and mixed uploads.

Drag & drop files here
Press Enter or Space to browse files, or drag and drop files here.
Drag & drop files here

Upload progress UI

If you enable uploads (via the hook’s upload option), you can surface progress with ItemProgress and status with ItemStatus.

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

Table and card layouts

Because the primitives are headless/composable, you can render your own layouts.

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
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

Dropzone sizes

Small

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

Default

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

Large

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
<FileUpload.Dropzone size="sm" />
<FileUpload.Dropzone size="default" />
<FileUpload.Dropzone size="lg" />

Avatar upload

Click or drag to upload avatar

export function AvatarUploadDemo() {
  const [state, actions] = useFileUpload({
    accept: "image/*",
    maxSize: 2 * 1024 * 1024,
    multiple: false,
  });

  return (
    <div className="flex flex-col items-center gap-4">
      <FileUpload.Root
        files={state.files}
        addFiles={actions.addFiles}
        removeFile={actions.removeFile}
        clearFiles={actions.clearFiles}
        openFileDialog={actions.openFileDialog}
        getInputProps={actions.getInputProps}
        handleDragEnter={actions.handleDragEnter}
        handleDragLeave={actions.handleDragLeave}
        handleDragOver={actions.handleDragOver}
        handleDrop={actions.handleDrop}
        isDragActive={state.isDragging}
        accept="image/*"
      >
        <FileUpload.Trigger hideDefaultContent>
          <FileUpload.Dropzone
            hideDefaultContent
            className="size-32 min-h-0 rounded-full p-0"
          >
            {state.files.length > 0 ? (
              <img
                src={state.files[0].preview}
                alt="Avatar"
                className="size-full rounded-full object-cover"
              />
            ) : (
              <div className="text-ppx-neutral-10 flex flex-col items-center justify-center">
                <UserIcon className="size-10" />
                <span className="mt-1 text-xs">Upload</span>
              </div>
            )}
          </FileUpload.Dropzone>
        </FileUpload.Trigger>
      </FileUpload.Root>
      <p className="text-ppx-neutral-10 text-xs">
        Click or drag to upload avatar
      </p>
    </div>
  );
}

Anatomy

The file upload primitives are designed to be composed together. Most apps follow this structure:

  • FileUpload.Root: Provides context for all child components
  • FileUpload.Dropzone: Drag-and-drop area (also supports paste and keyboard)
  • FileUpload.Trigger: Standalone “open file dialog” button
  • FileUpload.ItemList / FileUpload.Item: Render selected files
  • FileUpload.ImageGrid / FileUpload.ImageGridItem: Render images in a grid
  • FileUpload.* item parts: ItemPreview, ItemName, ItemSize, ItemStatus, ItemProgress, ItemError, ItemRetry, ItemRemove
  • FileUpload.ClearButton: Clear all selected files

API Reference

FileUploadField

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

PropTypeDefaultDescription
variant"dropzone" | "button" | "compact""dropzone"Layout variant
size"sm" | "default" | "lg""default"Dropzone size (dropzone variant)
dropzoneTextstring"Paste Or Drag & Drop Files Here"Dropzone label text
buttonTextstring"Browse for files"Trigger button label
showFileListbooleantrueShow file list below the control
showImageGridbooleanfalseShow an image grid instead of a list
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, onFilesAdded, and upload.


useFileUpload

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

Options (partial):

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
onFilesAdded(addedFiles) => void-Called with only newly-added files
uploadUploadConfig-Enables uploads and progress tracking

Returns: [state, actions]

  • State: { files, isDragging, errors, isUploading }
  • Actions: addFiles, removeFile, clearFiles, clearErrors, openFileDialog, getInputProps, drag handlers, plus upload actions uploadFiles, retryUpload, cancelUpload.

FileUpload.Root

Context provider and wiring point for useFileUpload.

PropTypeRequiredDescription
filesFileUploadFile[]YesFiles from useFileUpload().files
addFiles(files: FileList | File[]) => voidYesAdds files
removeFile(id: string) => voidYesRemoves a file
clearFiles() => voidYesClears all files
retryUpload(id: string) => Promise<void>NoEnables retry UI (ItemRetry, image-grid retry overlay)
openFileDialog() => voidYesOpens native file picker
getInputProps(props?) => InputPropsYesProps for the internal <input type="file" />
handleDragEnter(e) => voidYesDrag enter handler
handleDragLeave(e) => voidYesDrag leave handler
handleDragOver(e) => voidYesDrag over handler
handleDrop(e) => voidYesDrop handler
isDragActivebooleanNoHook’s isDragging state
isUploadingbooleanNoHook’s isUploading state
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, 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
hideDefaultContentbooleanfalseHide built-in UI so you can render custom children

Inherited Props: All native div props.


FileUpload.Trigger

Standalone trigger button for opening the file dialog.

PropTypeDefaultDescription
uploadingTextstring"Uploading..."Text shown when showUploadingState and uploading
showUploadingStatebooleantrueDisable + show spinner/text during uploads

Inherited Props: All Button props.


FileUpload.ItemList

Renders nothing when there are no files.

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

Inherited Props: All native div props.


FileUpload.Item

Wraps a file row and provides item context for Item* parts.

PropTypeDefaultDescription
fileFileUploadFile-The file object (required)
statusStylesbooleantrueApply error styling when status is "error"

Inherited Props: All native div props.


FileUpload.ItemPreview

PropTypeDefaultDescription
fallbackReact.ReactNode<FileIcon />Fallback when no preview is available

Inherited Props: All native div props.


FileUpload.ItemStatus

PropTypeDefaultDescription
successIconReact.ReactNode<CheckIcon />Custom success icon
uploadingContentReact.ReactNode"{progress}%"Custom uploading content
errorContentReact.ReactNode"Failed"Custom error content

Inherited Props: All native div props.


FileUpload.ImageGrid

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

Inherited Props: All native div props.


FileUpload.ImageGridItem

PropTypeDefaultDescription
fileFileUploadFile-The file object (required)
showStatusOverlaybooleantrueShow uploading/error/success overlays

Inherited Props: All native div props.