Files
File uploads with progress, cancel and retry, a file table, storage by kind with the largest files, and a folder browser.
The dropzone wraps a real file input, so it opens the file picker on click, on tap and from the keyboard, and takes files dragged onto it. It checks type, size and count before anything is sent, and lists files it turns away with the reason. useFileUploads works with any storage: pass an upload function that sends one file, reports progress and stops when its signal aborts. Uploads run three at a time by default. File icons and colours come from the name or MIME type, sizes count in steps of 1,024, and times take a fixed now so they render the same on the server and in the browser.
Upload
A dropzone with the accepted types and size limit, and each upload's progress. Cancel an upload while it runs, retry one that failed, or remove it. The example fails every third upload once, to show retry.
2 of 3 uploaded, 1 failed
fall-launch-brief.pdf
2.4 MB ·Uploaded
hero-banner@2x.png
6.8 MB ·Uploaded
launch-film-raw-footage.zip
Larger than 25 MB
File Table
Files with their type, owner, last change and size. Search, filter by type, sort, and download or delete a selection. Each row has a menu to download, copy a link or delete. Built on the Data Table.
| Actions | ||||||
|---|---|---|---|---|---|---|
| Q3 board deck.pptx | Presentation | PNPriya Nair | 18.4 MB | |||
| Fall launch brief.pdf | MSMateo Silva | 2.4 MB | ||||
| Campaign budget FY27.xlsx | Spreadsheet | HOHannah Okafor | 486 KB | |||
| hero-banner@2x.png | Image | LFLena Fischer | 6.8 MB | |||
| Brand guidelines v4.pdf | PNPriya Nair | 12.1 MB | ||||
| Launch teaser 30s.mp4 | Video | LFLena Fischer | 142 MB | |||
| Press release draft.docx | Document | MSMateo Silva | 92 KB | |||
| Podcast ep 12 master.wav | Audio | JBJonas Berg | 318 MB | |||
| Customer logos.zip | Archive | HOHannah Okafor | 54.6 MB | |||
| utm-builder.json | Code | JBJonas Berg | 6 KB |
Storage
Storage used against the plan, split by kind of file, with the largest files to clean up. Deleting one frees its space. Builds on the Usage Meter primitives.
- Videos38.2 GB
- Images21.7 GB
- Documents11.4 GB
- Archives7.9 GB
- Other3.4 GB
Largest files
- Summit keynote full recording.mov6.2 GB · Events / Summit 20266.2 GB
- Product shoot RAW archive.zip3.8 GB · Brand / Photography3.8 GB
- Launch teaser 4K master.mp42.1 GB · Campaigns / Fall launch2.1 GB
- Spring catalog print-ready.pdf846 MB · Sales / Print846 MB
Folder Browser
Folders and files in a grid, with a path back to the top. Open a folder to go into it; each file has a menu.
- Q3 board deck.pptx18.4 MB ·
- Campaign budget FY27.xlsx486 KB ·
- Press release draft.docx92 KB ·
- Podcast ep 12 master.wav318 MB ·
- Team offsite.jpg4.6 MB ·
Usage
// fetch can't report upload progress, so this uses XMLHttpRequest.
const upload: UploadFn = (file, { onProgress, signal }) =>
new Promise((resolve, reject) => {
const request = new XMLHttpRequest()
request.open('POST', '/api/files')
request.upload.onprogress = (event) => onProgress(event.loaded / event.total)
request.onload = () =>
request.status < 300 ? resolve() : reject(new Error('Upload failed. Try again.'))
request.onerror = () => reject(new Error('Connection lost. Try again.'))
signal.addEventListener('abort', () => request.abort())
const body = new FormData()
body.append('file', file)
request.send(body)
})
function Attachments() {
const { add, cancel, remove, retry, uploads } = useFileUploads({ upload })
return (
<>
<FileDropzone accept='.pdf,image/*' maxSize={10 * 1024 * 1024} onFiles={add} />
<FileUploadList>
{uploads.map((item) => (
<FileUploadItem
key={item.id}
upload={item}
onCancel={cancel}
onRemove={remove}
onRetry={retry}
/>
))}
</FileUploadList>
</>
)
}upload resolves when the file is stored, optionally with its url, and rejects with an Error whose message the row shows. For S3, R2 or another store, upload to a signed URL the same way, or call your storage SDK and pass its progress to onProgress.