Unified UI

File Upload

A drag-and-drop file upload zone with file list, progress tracking, image preview thumbnails, and animated transitions.

Basic

A production-ready drag-and-drop file upload zone with file list, progress bars, image preview thumbnails, size validation, and stagger animations. Supports single and multiple file uploads.

Click to upload or drag and drop

image/*,.pdf

Installation

Install the component via the CLI in one command.

npx @work-rjkashyap/unified-ui add file-upload
pnpm dlx @work-rjkashyap/unified-ui add file-upload
npx @work-rjkashyap/unified-ui add file-upload
bunx @work-rjkashyap/unified-ui add file-upload

If you haven't initialized your project yet, run npx @work-rjkashyap/unified-ui init first. See the CLI docs for details.

Or install the full package

Use this approach if you prefer to install the entire design system as a dependency instead of copying individual components.

npm install @work-rjkashyap/unified-ui
pnpm add @work-rjkashyap/unified-ui
yarn add @work-rjkashyap/unified-ui
bun add @work-rjkashyap/unified-ui

Anatomy

import { FileUpload } from "@work-rjkashyap/unified-ui";

Basic Usage

Drop FileUpload anywhere you need a file input. It manages its own internal file list by default (uncontrolled). Users can click the zone or drag and drop files onto it.

<FileUpload onFilesChange={(files) => console.log(files)} />

Sizes

Click to upload or drag and drop

Click to upload or drag and drop

Click to upload or drag and drop

SizeMin HeightUse Case
sm120pxCompact forms, sidebars
md160pxDefault — most contexts
lg200pxFull-page upload, hero areas

Accept Filter

Restrict accepted file types using the standard HTML accept attribute syntax:

Click to upload or drag and drop

image/*

Click to upload or drag and drop

.pdf,.doc,.docx

<FileUpload accept="image/*" description="Images only" />
<FileUpload accept=".pdf,.doc,.docx" description="PDF and Word documents" />
<FileUpload accept="video/*" description="Video files" />

Max File Size

Set maxSize in bytes to validate file size. Files exceeding the limit are rejected with an error message.

<FileUpload
  maxSize={10 * 1024 * 1024}
  description="Max 10MB per file"
/>

Max Files

Limit the total number of files that can be uploaded with maxFiles:

<FileUpload
  maxFiles={5}
  description="Up to 5 files"
/>

Single File

Set multiple={false} to allow only one file at a time:

Click to upload or drag and drop

Custom Label

Override the default "Click to upload or drag and drop" text with the label prop:

Drop your resume here

.pdf,.docx · max 5.0 MB

Disabled State

Click to upload or drag and drop

Handling File Events

Use the callback props to react to file changes:

<FileUpload
  accept="image/*"
  maxSize={5 * 1024 * 1024}
  maxFiles={3}
  description="Images up to 5MB, max 3 files"
  onFileAdd={(file) => {
    console.log("Added:", file.name);
    startUpload(file);
  }}
  onFileRemove={(id) => {
    console.log("Removed:", id);
    cancelUpload(id);
  }}
  onFilesChange={(files) => {
    console.log("All files:", files);
    setFormFiles(files);
  }}
/>

Upload Progress Pattern

The FileUploadItem type includes progress and status fields for tracking upload state. You can manage these externally and pass updated items:

const [files, setFiles] = useState<FileUploadItem[]>([]);

const handleFileAdd = async (file: File) => {
  const id = crypto.randomUUID();
  const item: FileUploadItem = {
    id,
    file,
    status: "uploading",
    progress: 0,
  };
  setFiles((prev) => [...prev, item]);

  try {
    await uploadFile(file, {
      onProgress: (progress) => {
        setFiles((prev) =>
          prev.map((f) => (f.id === id ? { ...f, progress } : f))
        );
      },
    });
    setFiles((prev) =>
      prev.map((f) => (f.id === id ? { ...f, status: "success", progress: 100 } : f))
    );
  } catch (err) {
    setFiles((prev) =>
      prev.map((f) => (f.id === id ? { ...f, status: "error", error: "Upload failed" } : f))
    );
  }
};

In a Form

A common pattern is wrapping FileUpload in a FormField for label, description, and error handling:

<FormField label="Attachments" description="Upload relevant documents.">
  <FileUpload
    accept=".pdf,.doc,.docx,.png,.jpg"
    maxFiles={5}
    maxSize={10 * 1024 * 1024}
    onFilesChange={setAttachments}
  />
</FormField>

Props

PropTypeDefaultDescription
onFilesChange(files: FileUploadItem[]) => voidCalled whenever the file list changes (add or remove).
onFileAdd(file: File) => voidCalled when a single file is added.
onFileRemove(id: string) => voidCalled when a file is removed by its ID.
acceptstringAccepted file types (MIME types or extensions, e.g. "image/*").
multiplebooleantrueAllow multiple files.
maxFilesnumber10Maximum number of files allowed.
maxSizenumberMaximum file size in bytes.
size"sm" | "md" | "lg""md"Size of the drop zone.
disabledbooleanfalseDisables the file upload.
labelReactNodeDefault textCustom label content for the drop zone.
descriptionReactNodeDescription text shown below the label.
classNamestringAdditional CSS classes on the wrapper element.
aria-labelstring"File upload"Accessible label for the drop zone region.

FileUploadItem

PropertyTypeDescription
idstringUnique identifier for this file item.
fileFileThe browser File object.
previewstring?Object URL for image thumbnails (auto-generated).
progressnumber?Upload progress (0–100).
errorstring?Error message if upload failed.
status"idle" | "uploading" | "success" | "error"Current upload status.

Zone States

The drop zone uses CVA variants for visual feedback:

StateDescription
idleDefault state — dashed border, hover highlight.
dragOverA file is being dragged over — primary border + tint.
errorA validation error occurred — danger border + tint.
disabledNon-interactive — dimmed opacity, no pointer events.

Motion

  • Upload icon — Scales up with a spring animation when a file is dragged over the zone.
  • File list items — Stagger animation using staggerContainerFast + slideUpSm for a cascading reveal when files are added.
  • File removal — Fades out with a scale-down transition via AnimatePresence.
  • Progress bar — Animated width transition for smooth progress updates.
  • All animations respect prefers-reduced-motion via useReducedMotion().

Accessibility

  • The drop zone renders as a role="region" with aria-label for screen readers.
  • The hidden <input type="file"> is properly associated and triggered by both click and keyboard.
  • The zone is keyboard-focusable (tabIndex={0}) and activates on Enter or Space.
  • Uses the design system's focus ring for visible keyboard focus indication.
  • Each file's remove button has a descriptive aria-label (e.g. "Remove document.pdf").
  • Error messages are visually prominent with an alert icon.
  • The disabled prop removes the zone from the tab order and prevents all interaction.

Design Tokens

TokenUsage
--borderDrop zone border color (idle)
--primaryDrop zone border/tint (drag over, link)
--dangerError state border/tint and error text
--mutedThumbnail placeholder background
--foregroundFile name text, label text
--muted-foregroundDescription text, file size, icon color
--accentRemove button hover background
--radius-mdFile item border radius
--radius-lgDrop zone border radius
--duration-fastTransition speed

On this page