Upload Progress
Per-file and aggregate progress bars for in-flight uploads, reading the ambient state of a useFiles instance.
A drop-in progress view. It reads the ambient uploads and progress a useFiles instance exposes, so it lights up for any upload that instance starts — wherever the call originates. Returns null when idle.
"use client";
import { UploadIcon } from "lucide-react";
import type { ChangeEvent } from "react";
import { useEffect, useRef } from "react";
import { Button } from "@/components/ui/button";
import { demoFiles } from "@/lib/demo-files";
import { UploadProgress } from "@/registry/files-sdk/upload-progress/upload-progress";
const Example = () => {
const files = demoFiles;
const inputRef = useRef<HTMLInputElement>(null);
// Read `files` via a ref so this one-shot effect never re-runs on store
// changes (see the file-list/file-preview note on the loop footgun).
const filesRef = useRef(files);
filesRef.current = files;
// Kick off a sample upload on mount so the progress UI starts populated.
useEffect(() => {
const bytes = new Uint8Array(3 * 1024 * 1024);
const sample = new File([bytes], "vacation-photo.jpg", {
type: "image/jpeg",
});
void filesRef.current.upload(sample);
}, []);
const handleChange = async (event: ChangeEvent<HTMLInputElement>) => {
// Capture the element now — React nulls `currentTarget` after the handler's
// synchronous phase, so it's gone by the time the uploads below resolve.
const input = event.currentTarget;
const picked = input.files;
if (!picked?.length) {
return;
}
for (const file of picked) {
// eslint-disable-next-line no-await-in-loop -- demo uploads files one at a time so progress reads cleanly
await files.upload(`demo/${file.name}`, file, {
contentType: file.type,
});
}
input.value = "";
};
return (
<div className="flex flex-col gap-4">
<div>
<Button
onClick={() => inputRef.current?.click()}
type="button"
variant="outline"
>
<UploadIcon />
Choose files
</Button>
<input
aria-label="Choose files to upload"
className="hidden"
multiple
onChange={(event) => void handleChange(event)}
ref={inputRef}
type="file"
/>
</div>
<UploadProgress files={files} />
</div>
);
};
export default Example;
Installation
npx shadcn@latest add https://files-sdk.dev/r/upload-progress.jsonUsage
import { useFiles } from "files-sdk/react";
import { UploadProgress } from "@/components/files-sdk/upload-progress";
export function Uploader() {
const files = useFiles({ endpoint: "/api/files" });
return (
<>
<button onClick={() => files.upload(/* … */)} type="button">
Upload
</button>
<UploadProgress files={files} />
</>
);
}
Each row shows a file’s name, percentage and status (uploading, success, error, aborted). When more than one file is in flight, an aggregate bar is shown on top.
Props
PropType
filesUseFilesResult
A `useFiles()` instance — reads its ambient `uploads` / `progress`.
Type
UseFilesResultclassName?string
Type
string