Skip to content
Template Empire
Browse components

Upload Queue Progress

A free upload progress queue react component with real progressbar semantics and one polite summary.

Upload progress

4 files: 2 uploading, 1 waiting, 1 failed.

  1. campaign-brief.pdf1.2 MBUploading 0%
  2. hero-render.png8.4 MBUploading 0%
  3. handover-notes.docx240 KBWaiting 0%
  4. voiceover-take-3.wav42 MBFailed

Customise

4px
0.35s
0.04s
8px
18px

Code

<LoaderUploadQueue files={files} onCancel={handleCancel} onRetry={handleRetry} />

Upload Queue Progress is a free upload progress queue react component for Next.js. Every row states its own progress and status in words, Cancel and Retry appear only where they can actually do something, and nothing in the component mutates the data it was given: an action reports an id and waits for you to decide what happens next.

The part worth copying is the announcement. One polite summary describes the queue, and it is built from how many rows are in each state rather than from their progress, so it is identical across every percentage tick and changes only when a row moves between states. A live region fed by progress talks several times a second and drowns out everything else on the page. Individual rows keep exact progressbar semantics while they are active and become plain status text once they are complete or failed, because a progressbar frozen at a value is a control that looks live and is not.

Install

Add the @te registry to your components.json once:

{
  "registries": {
    "@te": "https://templateempire.io/r/{name}.json"
  }
}

Then install the component:

npx shadcn@latest add @te/loader-upload-queue

Usage

"use client";

import { useState } from "react";

import { LoaderUploadQueue } from "@/components/loader-upload-queue";

export default function Page() {
  const [files, setFiles] = useState([
    { id: "a", name: "brief.pdf", size: "1.2 MB", progress: 42, status: "uploading" },
    { id: "b", name: "notes.docx", size: "240 KB", progress: 0, status: "queued" },
  ]);

  return (
    <LoaderUploadQueue
      files={files}
      onCancel={(id) => setFiles((rows) => rows.filter((row) => row.id !== id))}
    />
  );
}

Props

PropTypeDefaultDescription
filesReadonlyArray<{ id: string; name: string; size: string; progress: number; status: "queued" | "uploading" | "complete" | "error" }>requiredOne to eight controlled rows with unique ids; duplicates are dropped, first wins. Progress is clamped to 0-100 and made to agree with status: queued shows 0, complete shows 100, and an error row keeps the value it was given.
onCancel(id: string) => voidundefinedSupply to enable Cancel on queued and uploading rows. Omit it and no Cancel button is rendered at all, rather than one that does nothing.
onRetry(id: string) => voidundefinedSupply to enable Retry on error rows. Omit it and no Retry button is rendered.
density"comfortable" | "compact""comfortable"Row spacing and padding.
barHeightnumber4Progress track height in CSS pixels, clamped to 2-10. The track holds its height whatever the fill is doing, so a row's box never moves.
settleDurationnumber0.35How long a state transition takes in seconds, clamped to 0-0.8. Also the duration of the completed check's draw.
staggernumber0.04Reveal stagger between newly added rows in seconds, clamped to 0-0.12.
iconTravelnumber8How far the state mark travels as it crossfades between states, in CSS pixels, clamped to 0-16.
radiusnumber18Queue and row corner radius in CSS pixels, clamped to 0-36.
labelstring"Upload progress"The visible heading, which is also the section's accessible name.
classNamestringundefinedAdditional classes merged onto the status section with cn().

Customisation

Upload Queue Progress declares three tokens of its own, each falling back to a global semantic token, so it retints from an ancestor without touching the component source. Geometry and timing come from props rather than tokens, because they are clamped: --upload-queue-accent colours the fill, the completed check and every focus ring, --upload-queue-track is the inactive part of the bar, and --upload-queue-error is the failure accent.

:root {
  --upload-queue-accent: var(--chart-2);
  --upload-queue-track: oklch(from var(--border) l c h / 0.8);
  --upload-queue-error: oklch(0.62 0.21 25);
}

<LoaderUploadQueue files={files} density="compact" barHeight={2} radius={8} />

Frequently asked questions

Is Upload Queue Progress free for commercial use?
Yes. Upload Queue Progress is MIT licensed, so you can use it in personal and commercial projects without attribution.
Does the live region announce every percentage change?
No, and the mechanism is worth understanding because it is easy to get wrong. The polite summary is built only from how many rows are in each state, never from their progress, so its text is byte-for-byte identical across every tick and there is nothing for assistive technology to announce. It changes exactly when a row moves between queued, uploading, complete and error, which is the only moment a listener needs telling. The exact percentages are still available on demand: each active row is a progressbar with aria-valuenow, and the number is visible beside it.
Is Upload Queue Progress accessible for keyboard and screen reader users?
Yes. The queue is a labelled section containing an ordered list, and each active row exposes a progressbar labelled by its own filename with min, max and now. Once a row completes or fails the progressbar is replaced by plain status text, because a progressbar stuck at a value looks live and is not. Status is always stated in words rather than by colour or icon alone, and every Cancel and Retry button includes its filename in its accessible name so a column of them is still navigable. With reduced motion enabled the stagger, the reflow, the icon travel and the check's draw are all dropped, and nothing about progress, status, actions or focus changes.

Related components

Want the full page this belongs to?Template Empire builds complete Next.js templates with this motion system throughout. Browse the paid templates that pair with these free components.Explore Template Empire templates