Upload Queue
import { UploadQueue } from '@absoluteui/components/upload-queue';import '@absoluteui/components/upload-queue/style.css';Using Angular? See Upload Queue for Angular.
What it does
Section titled “What it does”A list of files being uploaded. It provides:
- a Progress Bar per item, with size and status text (queued, uploading, done, failed, cancelled);
- cancel, retry, and remove actions per item;
- a transport-agnostic upload interface: you supply
upload(file, { signal, onProgress, attempt, id }); the queue schedules calls with a concurrency limit, cancels throughAbortController, and retries with exponential backoff; - polite announcements for failures, cancellations, removals, and batch completion;
- optional image previews through object URLs, revoked when items are removed or the queue is destroyed.
The queue never performs a network request. Only your upload function does. It works with any source of File objects; File Upload can feed it, but neither needs the other.
When to use
Section titled “When to use”- Uploading files with visible progress and the ability to cancel or retry.
- Chunked or resumable uploads (the consumer function loops over chunks and reports progress).
- Together with File Upload, or on its own with files from any source.
When not to use
Section titled “When not to use”- A single small file submitted with the form: submit it natively with File Upload.
- Background sync that must survive page reloads: the queue lives in memory.
Examples
Section titled “Examples”Queue fed by File Upload, uploading with fetch
Section titled “Queue fed by File Upload, uploading with fetch”import { FileUpload, type FileUploadChangeDetail } from '@absoluteui/components/file-upload';import { UploadQueue } from '@absoluteui/components/upload-queue';
const pickerHost = document.querySelector<HTMLElement>('#picker');const queueHost = document.querySelector<HTMLElement>('#queue');if (pickerHost && queueHost) { const picker = FileUpload.create(pickerHost, { label: 'Documents', multiple: true, maxFileSize: 50_000_000 }); const queue = UploadQueue.create(queueHost, { label: 'Uploads', upload: async (file, { signal }) => { const response = await fetch('/api/files', { method: 'POST', body: file, signal }); if (!response.ok) throw new Error(String(response.status)); return response.json(); }, }); picker.host.addEventListener('aui-change', (e) => { queue.add((e as CustomEvent<FileUploadChangeDetail>).detail.added); picker.setValue([]); });}Chunked upload with progress and retries
Section titled “Chunked upload with progress and retries”import { UploadQueue, type UploadFunction } from '@absoluteui/components/upload-queue';
const CHUNK = 5_000_000;const upload: UploadFunction = async (file, { signal, onProgress, id }) => { for (let offset = 0; offset < file.size; offset += CHUNK) { const chunk = file.slice(offset, offset + CHUNK); const response = await fetch(`/api/uploads/${id}?offset=${String(offset)}`, { method: 'PUT', body: chunk, signal }); if (!response.ok) throw new Error(String(response.status)); onProgress(Math.min(offset + CHUNK, file.size)); } return null;};
const host = document.querySelector<HTMLElement>('#queue');if (host) UploadQueue.create(host, { label: 'Uploads', upload, concurrency: 3, retries: 2, errorMessage: () => 'The file could not be uploaded. Try again.' });import { UploadQueue, type UploadQueueCompleteDetail } from '@absoluteui/components/upload-queue';import '@absoluteui/components/progress-bar/style.css';import '@absoluteui/components/upload-queue/style.css';
const host = document.querySelector<HTMLElement>('#queue');if (host) { const queue = UploadQueue.create(host, { label: 'Photos', preview: true, retries: 1, upload: (file, { signal, onProgress }) => new Promise((resolve, reject) => { const xhr = new XMLHttpRequest(); xhr.open('POST', '/api/photos'); xhr.upload.onprogress = (e) => { onProgress(e.loaded, e.total); }; xhr.onload = () => { if (xhr.status < 300) resolve(xhr.response); else reject(new Error(String(xhr.status))); }; xhr.onerror = () => { reject(new Error('network')); }; signal.addEventListener('abort', () => { xhr.abort(); reject(new DOMException('Aborted', 'AbortError')); }); xhr.send(file); }), }); queue.host.addEventListener('aui-complete', (e) => { const { failed } = (e as CustomEvent<UploadQueueCompleteDetail>).detail; if (failed === 0) queue.clearCompleted(); });}More usage
Section titled “More usage”import { UploadQueue } from '@absoluteui/components/upload-queue';
const host = document.createElement('div');document.body.append(host);const queue = UploadQueue.create(host, { label: 'Uploads', upload: async (file, { signal }) => { const r = await fetch('/api/files', { method: 'POST', body: file, signal }); if (!r.ok) throw new Error(String(r.status)); return r.json(); },});queue.add([new File(['hello'], 'hello.txt', { type: 'text/plain' })]);More examples (File Upload integration, chunked upload), type-checked in CI: metadata/upload-queue/examples.json.
Anatomy
Section titled “Anatomy”<div class="aui-upload-queue"> <div class="aui-upload-queue__label" id="L">Uploads</div> <div class="aui-upload-queue__summary">1 of 2 uploaded</div> <ul class="aui-upload-queue__list" aria-labelledby="L" tabindex="-1"> <li class="aui-upload-queue__item" data-status="uploading"> <span class="aui-upload-queue__media"><svg aria-hidden="true"/> | <img alt="" src="blob:…"></span> <div class="aui-upload-queue__progress"> (Progress Bar: label = file name, description = "2 MB · Uploading")</div> <div class="aui-upload-queue__actions"> <button class="aui-upload-queue__action aui-upload-queue__cancel" aria-label="Cancel upload of a.pdf"> <button class="… aui-upload-queue__retry" aria-label="Retry upload of a.pdf" hidden> <button class="… aui-upload-queue__remove" aria-label="Remove a.pdf" hidden> </div> </li> </ul></div>API reference
Section titled “API reference”Factory
Section titled “Factory”UploadQueue.create(host: HTMLElement, options: UploadQueueOptions): UploadQueueGlobal configuration key: uploadQueue (see Configuration).
Options
Section titled “Options”| Option | Type | Default | Description |
|---|---|---|---|
label |
string |
Visible label and accessible name of the list. Required. | |
upload |
(file: File, context: { signal: AbortSignal; onProgress: (loaded: number, total?: number) => void; attempt: number; id: string }) => Promise<unknown> |
Transport: performs one attempt. Resolve on success (the value becomes item.response), reject on failure, honour signal. Required. | |
preview |
boolean |
false |
Image previews via object URLs for image/* files (needs img-src blob:). |
errorMessage |
(error: unknown, file: File) => string | null |
Map a rejection to a user-facing message. Default: generic localized message. | |
icons |
Partial<{ file: IconRef; cancel: IconRef; retry: IconRef; remove: IconRef }> |
Icon slot overrides. | |
concurrency |
number |
2 |
Uploads running at the same time (1..16). Globally configurable. |
retries |
number |
0 |
Automatic retries after a failed attempt (0..10). Globally configurable. |
retryDelay |
number |
1000 |
Delay before the first automatic retry in ms (0..60000); doubles for each further retry. Globally configurable. |
autoStart |
boolean |
true |
Start uploading as soon as files are added; when false, call start(). Globally configurable. |
locale |
string |
"en-US" |
Message and size-formatting locale. Globally configurable. Inherited from common configuration. |
Properties
Section titled “Properties”| Property | Type | Description |
|---|---|---|
host |
HTMLElement |
Host element. |
list |
HTMLUListElement |
The list element. |
items |
readonly UploadItem[] |
Snapshots of all items in queue order. |
busy |
boolean |
Any item queued or uploading. |
options |
Readonly<UploadQueueOptions> |
Current options. |
Methods
Section titled “Methods”| Method | Description |
|---|---|
add(files: Iterable<File>): string[] |
Queue files; returns item ids. |
start(): void |
Start queued items (autoStart: false). |
cancel(id: string): void |
Cancel a queued or uploading item (aborts its signal). |
cancelAll(): void |
Cancel every active item. |
retry(id: string): void |
Queue a failed or cancelled item again. |
remove(id: string): void |
Remove an item (aborts it; revokes its preview URL). |
clear(): void |
Remove every item. |
clearCompleted(): void |
Remove finished items. |
getItem(id: string): UploadItem | null |
One item snapshot. |
update(options: Partial<UploadQueueOptions>): void |
Merge and apply options. |
destroy(): void |
Abort all uploads, revoke preview URLs, remove rendering. Late results are ignored. |
Events
Section titled “Events”| Event | Detail | Bubbles | Cancelable | Description |
|---|---|---|---|---|
aui-change |
{ items: readonly UploadItem[]; reason: 'added' | 'started' | 'succeeded' | 'failed' | 'cancelled' | 'retried' | 'removed'; item: UploadItem | null } |
Yes | No | Items were added or removed, or an item changed status (not emitted for progress). |
aui-complete |
{ succeeded: number; failed: number; cancelled: number } |
No | No | Nothing is queued or uploading any more (once per batch). |
CSS classes
Section titled “CSS classes”All rules live in the aui.components cascade layer, so application styles override them without specificity battles.
| Class | Purpose |
|---|---|
aui-upload-queue |
Host. |
aui-upload-queue__label |
Visible label. |
aui-upload-queue__summary |
“{done} of {total} uploaded” or the empty text. |
aui-upload-queue__list |
List (focus fallback, tabindex -1). |
aui-upload-queue__item |
Row; data-status holds the status. |
aui-upload-queue__media |
File icon or preview. |
aui-upload-queue__preview |
Preview image. |
aui-upload-queue__progress |
Progress Bar host. |
aui-upload-queue__actions |
Row actions. |
aui-upload-queue__action | __cancel | __retry | __remove |
Action buttons. |
Icon slots
Section titled “Icon slots”| Slot | Default icon |
|---|---|
file |
file |
cancel |
close |
retry |
refresh |
remove |
close |
Design tokens
Section titled “Design tokens”Tokens this stylesheet reads (verified in CI). Override them in a theme or with the Theme Designer.
--aui-border-width | --aui-color-border-strong | --aui-color-error-fg | --aui-color-focus-ring | --aui-color-hover | --aui-color-surface | --aui-color-surface-sunken | --aui-color-text | --aui-color-text-muted | --aui-focus-ring-offset | --aui-focus-ring-width | --aui-font-family-sans | --aui-font-size-sm | --aui-font-weight-medium | --aui-icon-size-md | --aui-icon-size-sm | --aui-line-height-tight | --aui-radius-md | --aui-radius-sm | --aui-space-1 | --aui-space-2 | --aui-space-3 | --aui-touch-target-min
TypeScript definitions
Section titled “TypeScript definitions”type UploadStatus = 'queued' | 'uploading' | 'done' | 'failed' | 'cancelled';interface UploadContext { readonly signal: AbortSignal; readonly onProgress: (loaded: number, total?: number) => void; readonly attempt: number; readonly id: string }type UploadFunction = (file: File, context: UploadContext) => Promise<unknown>;interface UploadItem { readonly id: string; readonly file: File; readonly status: UploadStatus; readonly loaded: number; readonly total: number; readonly attempts: number; readonly error: string | null; readonly response: unknown }interface UploadQueueConfig { concurrency: number; retries: number; retryDelay: number; autoStart: boolean }interface UploadQueueOptions extends Partial<UploadQueueConfig> { label: string; upload: UploadFunction; preview?: boolean; errorMessage?: (error: unknown, file: File) => string | null; locale?: string; icons?: Partial<UploadQueueIcons>;}interface UploadQueueChangeDetail { items: readonly UploadItem[]; reason: UploadQueueChangeReason; item: UploadItem | null }interface UploadQueueCompleteDetail { succeeded: number; failed: number; cancelled: number }Accessibility
Section titled “Accessibility”Pattern: List of Progress Bars (ARIA progressbar) with native action buttons and a polite live region for outcomes.
Keyboard
Section titled “Keyboard”| Key | Context | Action |
|---|---|---|
| Tab / Shift+Tab | Any | Moves through the visible action buttons of each row. |
| Enter / Space | Action focused | Cancels, retries, or removes. When the focused action disappears, focus moves to the first action of the same row; after removal, to the next row, else the list. |
| Element | Role |
|---|---|
| list | list (native <ul>, aria-labelledby the label) |
| item progress | progressbar (Progress Bar), named by the file name |
| actions | button (native), named per file |
| preview image | img with alt=“” (decorative; the name is adjacent) |
ARIA attributes
Section titled “ARIA attributes”| Attribute | Element | Value |
|---|---|---|
aria-labelledby |
list | Label id |
aria-describedby |
progressbar | Size and status text, including the error |
aria-label |
actions | “Cancel upload of {file}”, “Retry upload of {file}”, “Remove {file}” |
Actions show a :focus-visible outline. Focus is never moved by progress or status changes, only after the user’s own action removed the focused control.
WCAG 2.2 success criteria addressed
Section titled “WCAG 2.2 success criteria addressed”Automated test results: Accessibility and compliance.
Notes and known caveats
Section titled “Notes and known caveats”- Announcements (polite): each failure and cancellation, removals, and one summary when the batch completes. Progress is never announced continuously.
- Status is text in each row’s description, and done/failed add Progress Bar tone icons with labels: never color alone.
- Manual screen-reader verification (NVDA, JAWS, VoiceOver) is pending.
Behavior
Section titled “Behavior”States
Section titled “States”Per item: queued, uploading, done, failed, cancelled. Queue: empty, busy.
State transitions
Section titled “State transitions”| From | Trigger | To |
|---|---|---|
| - | add() |
queued (starts at once with autoStart when a slot is free) |
queued |
free slot | uploading; upload() called with attempt 1 |
uploading |
promise resolves | done; response stored |
uploading |
promise rejects, retries left | uploading again after retryDelay x 2^n (progress resets) |
uploading |
promise rejects, no retries left | failed; announced |
queued / uploading |
cancel() |
cancelled; signal aborted; announced |
failed / cancelled |
retry() |
queued |
| any | remove() |
removed; signal aborted; preview revoked |
Accessibility notes
Section titled “Accessibility notes”- Progress is never announced continuously. Announced (polite): each failure (“a.pdf failed to upload.”), each cancellation, removals, and one summary per batch (“All 3 files uploaded.” or “Uploads finished: 2 uploaded, 1 failed, 0 cancelled.”).
- Status is text; done and failed rows add the Progress Bar tone icon with a label, so status never depends on color.
- Focus moves only after the user’s own action removed the focused control.
- Manual screen-reader verification is pending.
Responsive behavior
Section titled “Responsive behavior”Full width. Media, progress (label and description wrap), and 24 px actions stay on one line; reflows at 320 CSS px.
Right-to-left
Section titled “Right-to-left”Logical properties: media at the inline start, actions at the inline end; progress fills from the inline start.
Theming
Section titled “Theming”Rows on color-surface with color-border-strong (color-error-fg when failed); Progress Bar tones: success when done, error when failed. Forced-colors mode draws CanvasText borders.
Global configuration
Section titled “Global configuration”configureAbsoluteUI({ components: { uploadQueue: { concurrency: 4, retries: 2, retryDelay: 2000 } } });Changes apply to existing queues (a higher concurrency starts waiting items).
Component configuration
Section titled “Component configuration”Instance options override global and theme defaults. All options are dynamic.
icons.file (default file), icons.cancel (close), icons.retry (refresh), icons.remove (close).
Security
Section titled “Security”- No network access by the library; your function controls the endpoint, credentials, and headers.
- Raw errors are never displayed (they may contain server internals): the default is “The upload did not complete.”; use
errorMessageto map known errors. - File names and messages are rendered with
textContent. - Previews: object URLs in
<img>(SVG in an image cannot run script). They needimg-src blob:in your Content-Security-Policy; without it previews are blocked by the browser and the rest works. URLs are revoked on remove, clear, and destroy.
Data handling
Section titled “Data handling”Files, progress, and responses stay in memory until removed or destroyed. Nothing is persisted, logged, or transmitted by AbsoluteUI.
Upload contract:
type UploadFunction = (file: File, context: { signal: AbortSignal; // aborted on cancel, remove, destroy onProgress: (loaded: number, total?: number) => void; // bytes; total defaults to file.size attempt: number; // 1, 2, … across automatic and manual retries id: string; // stable item id (upload session key)}) => Promise<unknown>; // resolve = done (value in item.response); reject = failed- Chunked uploads: call
onProgress(bytesSentSoFar)after each chunk; useidandattemptto resume on the server. - Cancellation is cooperative: pass
signaltofetchor callxhr.abort()onabort. - A synchronous throw counts as a failure; a non-promise return value counts as success.
Performance
Section titled “Performance”Status changes render the affected row at once; progress is coalesced to one render per animation frame. Budget: add() of 200 files < 100 ms (headless Chromium, e2e performance suite).
Supported data scale
Section titled “Supported data scale”Tested with 200 items. Keep concurrency modest (browsers limit parallel connections per host).
Edge cases
Section titled “Edge cases”onProgressvalues that are not finite are ignored; values above the total are clamped. A total of 0 shows an indeterminate bar while uploading.- A result that arrives after cancel, remove, retry, or destroy is ignored.
- A cancelled item keeps its concurrency slot until your function settles.
- Adding an empty iterable does nothing;
add()afterdestroy()returns[].
Errors
Section titled “Errors”| Code | Category | Cause |
|---|---|---|
UPLOAD_QUEUE_OPTIONS |
configuration | Missing label or upload function, invalid errorMessage/preview, non-File values passed to add(). |
CFG_INVALID_VALUE / CFG_UNKNOWN_OPTION |
configuration | Invalid concurrency, retries, retry delay, or unknown configuration. |
Upload failures are item states (failed), not exceptions. A failed construction leaves the host untouched.
Known limitations
Section titled “Known limitations”- Cancellation depends on your function honouring
signal. - Retries restart the attempt; resuming is your function’s job.
- Individual successes are included in the batch summary rather than announced one by one.
- Manual screen-reader verification is pending.
Unsupported scenarios
Section titled “Unsupported scenarios”- Built-in transports (fetch, XHR, tus, S3 multipart).
- Reordering items; persistence across reloads.
- HTML in file names or error messages.