Clinia UI

File Upload

A drop zone and a file list for uploads: drag or browse, with progress, success and errors per file.

When to use

  • Upload one or more files, such as lab results, scans or a CSV of records, by dragging or browsing.

When not to use

  • Pasting text or a URL: use Input or Textarea.
  • A single inline file field in a dense form: use Input with type="file".

Install

npx shadcn@latest add @clinia/file-upload
  • lab-results-2025-06.pdf471 KB
    Uploaded
  • intake-form-scan.png45%
    x

Anatomy

  • lab-results-2026-09.pdf45%
    x
  1. 1Drop zonedata-slot="file-upload"border-dashed · border-input
  2. 2File, with its statusdata-slot="file-upload-item"border-border · bg-card

Clinia rules

  • The drop zone is a dashed input border at rest. On drag-over it turns solid Trust over a bg-accent/15 tint.
  • Say what's accepted in description: the types and the size limit (PDF or image, up to 5 MB each.). Set the same limits with accept and maxSize.
  • Each rejected file gets a row with the reason (Larger than 5.0 MB.). Never drop a file silently.
  • Uploading shows a percentage and an xs Progress bar; done shows Uploaded with a check; an error shows the reason and a Retry button when you pass onRetry.

Do and don't

Do say what's accepted before anyone drops a file.
Don't leave the limits for users to discover by failing.
  • intake-scan.tiff8.0 MB
    Larger than 5 MB.
Do give each failed file its reason.
  • intake-scan.tiff8.0 MB
    Something went wrong.
Don't report a failure with no reason.

States

idle
drag over
focus
disabled
uploading
  • intake-form.png45%
    x
done
  • lab-results.pdf471 KB
    Uploaded
error
  • scan.tiff9.0 MB
    Larger than 5.0 MB.

Focus cells apply the component's focus-visible classes statically. Toggle the site theme to review both themes.

Accessibility

Built in

  • The drop zone is a label around a real <input type="file">, so Tab reaches it and Enter or Space opens the picker. Screen readers get the native control.
  • Each file's status is written out (a percentage, "Uploaded", or the error) with an icon, never shown by colour alone. Errors are announced with role="alert".
  • Drag-over switches the dashed border to solid Trust, at least 3:1 in both themes, the same cue as other selected states.
  • The component validates accept and maxSize and hands you the accepted and rejected files. Sending them is up to your app.

Your responsibility

  • If the upload is required or scoped, say so above the drop zone in a Field label, such as Lab results.
  • Pass onRetry for errors that a second try can fix, such as a network failure. Leave it out when the file itself is the problem.
  • Move focus somewhere sensible after a file is removed, such as the next file's remove button or the drop zone.

How to test

  1. Press Tab to reach the drop zone: the Trust ring appears around it. Press Enter or Space: the file picker opens.
  2. Pick a file that's too large: a row appears with the reason, and a screen reader announces it.
  3. Tab to a file's remove button: it's announced as Remove plus the file name.
  4. With a screen reader, an uploading file announces its name and progress.

API

FileUpload

PropValuesDefaultDescription
accept
string
–

File types the picker offers and the drop zone accepts, in the native accept format: .pdf,image/*. Other files come back in rejected.

disabled
boolean
–

Turns off the picker and the drop zone.

multiple
boolean
–

Takes every file picked or dropped. Without it, only the first file is kept.

maxSize
number
–

Largest accepted file, in bytes. Larger files come back in rejected.

onFiles
((accepted: File[], rejected: FileRejection[]) => void)
–

Accepted files first, then the ones that failed accept or maxSize, with the reason.

description
React.ReactNode
–

Hint under the prompt: accepted types and the size limit.

FileUploadItem

PropValuesDefaultDescription
name
string
–

The file name, shown truncated and used in the remove and retry labels.

size
number
–

In bytes; shown as B, KB or MB.

status
uploadingdoneerror
–

Each status shows its own text and icon: a percentage, "Uploaded", or the error.

progress
number
0

0 to 100 while uploading.

error
string
–

The reason shown when status is error.

onRemove
(() => void)
–

Shows a remove button, labelled with the file name.

onRetry
(() => void)
–

Shows a retry button when status is error.

All other props come from the native input element.