File Upload

Pro

The uploading experience: a drag-and-drop dropzone, a browse trigger, import shortcuts and built-in accept, size and count validation. Pairs with File Card for the resulting rows and per-file progress.

Drag and drop your files, or
brand-guidelines.pdf
2.3 MB
q3-forecast.xlsx
5.0 MB · Upload failed

Installation#

# One-time setup (tokens + lib helpers)npx koalaui-cli@latest init# Activate your Pro license (once per machine)npx koalaui-cli@latest login <key># Add this component (its dependencies come along)npx koalaui-cli@latest add file-upload

Usage#

File Upload owns selection: it emits accepted files through onFiles and rejections through onReject, and you own the list. Render the resulting rows with File Card inside a FileUploadList - the two compose into the full experience.

This example is part of Koala UI Pro

The preview is yours to explore. Its code comes with a Pro license, paid once and used in every project.

Have a license? Unlock it here

Anatomy#

import {  FileUpload,  FileUploadDropzone,  FileUploadIcon,  FileUploadTitle,  FileUploadLink,  FileUploadDescription,  FileUploadTrigger,  FileUploadSources,  FileUploadSource,  FileUploadList,  FileUploadGrid,} from "@/components/ui/file-upload"<FileUpload>  <FileUploadDropzone>    <FileUploadIcon />    <FileUploadTitle>      <FileUploadLink />    </FileUploadTitle>    <FileUploadDescription />    <FileUploadTrigger />    <FileUploadSources>      <FileUploadSource />    </FileUploadSources>  </FileUploadDropzone>  <FileUploadList />  <FileUploadGrid /></FileUpload>

The FileUpload root holds a hidden file input and the shared state; the FileUploadDropzone is the dashed drop surface, filled with a FileUploadIcon, a FileUploadTitle with the FileUploadLink sitting inline in the sentence, and a FileUploadTrigger (or a FileUploadSources row).

The panel is a drop target, not a button. Clicking anywhere on it still opens the picker, but the keyboard path runs through the real controls inside it, so every dropzone must compose a FileUploadLink or a FileUploadTrigger. A role="button" panel wrapping those would be a nested-interactive violation and would swallow their Enter/Space.

Drag and drop your files, or

Import shortcuts#

The local disk isn’t the only place a file lives. Introduce a FileUploadSources row with a labelled Divider and give each provider a FileUploadSource tile. A source owns its own picker - an OAuth flow, a provider SDK, your own modal - so it takes a onClick and deliberately does not route back into the native file dialog.

The mark is children, not a brand prop: pass whatever you own - a real logo, an img, or a Koala glyph.

Drag and drop your files, or

Thumbnail layout#

When the upload is the picture, a filename row gets in the way. Swap FileUploadList for FileUploadGrid and give each File Card layout="tile": the thumbnail fills the square and FileCardActions become a scrim that fades in on hover or keyboard focus. The tile never moves - the photo and the scrim carry the whole interaction.

Drag and drop your images, or
A-frame cabin among autumn trees
Lakeside cabin at dusk
Sunlit forest canopy

Row states#

A row in flight reads either way. Put the bar under the name with <FileCardProgress hideValue /> and move the percentage out to a FileCardValue on the right edge; or drop the bar for a plain FileCardMeta line when the pause and cancel controls matter more than the number. Once it lands, FileCardStatus reads the card’s state and lights the check on its own.

KoalaUIFinalDesign.jpg
60%
KoalaUIFinalDesign.jpg
182.2 MB · 60%
KoalaUIFinalDesign.jpg
182.2 MB

Browse button#

No room for a dropzone? Drop a FileUploadTrigger on its own - it opens the same picker, so an attach action is a single button. The trigger reuses the DS Button, so it inherits every variant and size.

No file selected

Avatar picker#

Compose the trigger with an Avatar for a profile-photo picker: a single-file image/* upload that swaps the avatar in place, with a “Change photo” / “Remove” pair.

JC

JPG, GIF or PNG. 2 MB max.

Validation#

Pass accept, maxSize (bytes), and multiple, and dropped or picked files are split for you: onFiles gets the keepers, onReject gets the rest with a reason (type, size, or count) so you can surface a precise message.

Drop a PDF here, or
Single PDF file, up to 2 MB

Density#

Density is Koala’s cross-cutting spacing axis (see Density). For File Upload it tunes the dropzone padding, the rhythm between its blocks, and the glyph. Density resolves prop → nearest DensityProvider → compact, so an unset dropzone renders compact; ask for comfortable to get the roomy panel.

comfortable · drop files, or
compact · drop files, or

Disabled#

Pass disabled to the root and the whole control goes inert - the dropzone stops accepting drops and clicks, the link, trigger and sources disable, and the surface dims.

Uploads paused
You’ve reached your storage limit

API reference#

FileUpload

The root: holds the hidden file input, validates every drop or pick, and shares the picker with its parts. It owns selection only; you keep the file list. Forwards native <div> props except onError; every part accepts className, merged last.

PropTypeDefaultDescription
onFiles(files: File[]) => void-Accepted files from a drop or browse. Fires only when at least one file passes.
onReject(rejections: FileRejection[]) => void-Rejected files, each { file, reason } with reason "type", "size" or "count".
acceptstring-Native input accept, e.g. "image/*,.pdf"; also checked against dropped files.
multiplebooleanfalseAllow more than one file per selection; without it, extra files are rejected with "count".
maxSizenumber-Max size per file, in bytes. Larger files are rejected with "size".
disabledbooleanfalseMake the whole control inert: the dropzone stops taking drops and clicks, and the link, trigger and sources disable.
density"comfortable" | "compact""compact"Dropzone padding, block rhythm and glyph size. Falls back to the nearest DensityProvider.
Data attributeDescription
data-disabledPresent while disabled.

FileUploadDropzone

The dashed drop surface; a click anywhere opens the picker. It is not a button, so compose a FileUploadLink or FileUploadTrigger inside as the keyboard path. Forwards all native <div> props.

Data attributeDescription
data-draggingPresent while a file is dragged over the panel.
data-disabledPresent while the root is disabled.

FileUploadIcon

The glyph crowning the dropzone, decorative and toned full ink while dragging. Defaults to an image placeholder; pass your own icon as children. Forwards all native <div> props.

FileUploadTitle

The dropzone's one balanced sentence, with a FileUploadLink inline. Forwards all native <div> props.

FileUploadDescription

Muted supporting copy, e.g. accepted types and size limit. Forwards all native <div> props.

FileUploadLink

The inline picker button inside the sentence, in full ink; renders "click here" when it has no children. Forwards all native <button> props.

FileUploadTrigger

A Button that opens the picker, inside the dropzone or on its own; renders "Browse files" when it has no children. Takes every Button prop.

PropTypeDefaultDescription
variantButtonProps["variant"]"outline"Any Button variant.

FileUploadSources

The layout-only "or import from" row of FileUploadSource tiles. Forwards all native <div> props.

FileUploadSource

One import shortcut: the provider's mark as children in a bordered tile, its name beneath. Wire onClick to that provider's picker; it never opens the local file dialog. Forwards all native <button> props.

PropTypeDefaultDescription
label*React.ReactNode-The name under the mark, e.g. "Google Drive"; also the button's accessible name.

FileUploadList

A layout-only column for the resulting FileCard rows; the exported formatBytes(bytes) turns file.size into a meta line ("1.8 MB"). Forwards all native <div> props.

FileUploadGrid

A layout-only grid (2 columns, 3 from sm) for image uploads; fill it with <FileCard layout="tile">. Forwards all native <div> props.

FAQ#

No. It owns selection only: accepted files arrive through `onFiles` and rejections through `onReject`, and you keep the list in your own state. Render that list with File Card inside `FileUploadList` (or `FileUploadGrid` for thumbnails).