Keep each file’s identity visible through selection, transfer and recovery. FileItem presents controlled state and emits action intent; the host owns transport and attempt identity.
A local simulation fails the first attempt. Cancel stops the current attempt; Retry completes a new one; Remove returns focus to the trigger.
| Example | State |
|---|---|
|
photo.png
Uploading · 45%
|
uploading |
scan.pdfUploading · total unknown | Unknown total |
|
archive.zip
File type is not accepted.
|
rejected |
|
brief.pdf
Connection lost. Retry upload.
|
failed |
|
draft.pdf
Upload cancelled.
|
cancelled |
|
invoice.pdf
Upload complete · 120 KB
|
complete |
|
very-long-project-filename-without-spaces.pdf
Selected · 12 KB
|
disabled |
| Verdict | Rule |
|---|---|
| Do | Keep filename and a text status visible in every state; rejected and failed entries explain why. Accepted means selected, complete means the host confirmed completion. |
| Do | Use Progress only with a known total, clamp percentages to 0–100 and update width and aria-valuenow together; use text for unknown totals. |
| Do | Expose Cancel only during upload, Retry only after failure or cancellation, and Remove outside active transfer. Rejected files can be removed, not retried unchanged. |
| Do | Keep action names specific to the filename. Actions are independent Buttons in a content row, not embedded control actions. |
| Do | Cancellation and removal invalidate pending callbacks before changing state; retry creates a new attempt so stale progress and completion cannot resurrect removed files. |
| Do | Restore focus to the next file action or picker after removal; announce concise state changes politely through the host, never every progress tick. |
| Do | Keep transport, AbortController ownership, persistence and retry policy in L2/host. FileItem emits intent and renders controlled state. |
| Don’t | Do not equate local selection or 100% bytes sent with server completion. |