A zero-dependency dropzone with a managed file list — click, drag, or paste; thumbnails, validation, progress, and retry. Every card below is live.
One file at a time; picking another replaces it. Keyboard: Tab to the zone, Enter or Space opens the picker.
new Upload('#box')
Drop a few images — previews render from object URLs (revoked on remove). Paste works too while the zone is focused.
new Upload('#box', {
multiple: true,
accept: 'image/*'
})
Images only, max 200 KB, max 3 files. Rejected files get an inline danger row — nothing throws, valid files still land.
new Upload('#box', {
multiple: true,
accept: 'image/*',
maxSize: 200 * 1024, // '200 KB' in the error
maxFiles: 3
})
A simulated uploader with progress. Files whose name contains
fail reject — the row gets a retry button (a retry
succeeds). Or use the buttons to add sample files.
new Upload('#box', {
multiple: true,
upload: function (file, onProgress) { // onProgress(0..1)
return fakeServer(file, onProgress) // → Promise
},
onDone: (file, res) => log(file.name + ' → ' + res.url)
})
// built-in XHR uploader instead:
// upload: { action: '/api/upload', fieldName: 'file' }
The original <input type=file> is wrapped and
hidden but keeps submitting with the form — dragged files are mirrored
onto it. Submit to see what the form would send.
new Upload(document.getElementById('attachments'))
// destroy() restores the native input exactly
With the VC core loaded, one call themes every component.
VC.config({ accent: '#b45309' })
// standalone: .vup { --vup-accent: #b45309 }