1 Pure CSS8 with JSPublished 2026-10-04Free · no sign-up
Nine file upload patterns built on the native file input: a custom Choose file button, a drag and drop zone, a file list with remove buttons, an upload with a progress bar, image previews, a chat attach button, a round photo picker, type and size validation and the simplest styled native input.
A custom Choose file button built on a real file input. The input is visually hidden but focusable, the label is styled as the button, and the chosen file name appears next to it, so the native behaviour stays intact.
A drop zone that highlights when a file is dragged over it and also works as a click target for people who cannot drag. Dropped and chosen files share one handler, and the zone states the accepted types and size limit in text.
View code
<label class="dz" id="dz">
<input type="file" multiple accept=".jpg,.png,.pdf">
<b>Drop files here or <u>browse</u></b>
<small>JPG, PNG or PDF, up to 10 MB each</small>
<span class="out" aria-live="polite"></span>
</label>
A file picker that lists every selected file with its size and a remove button. Sizes are formatted in KB or MB, each remove button is named after its file, and the list is rebuilt from a DataTransfer so the input matches what is shown.
A file row that shows upload progress after a file is chosen, with a simulated transfer in this demo. The progress element announces its value, a cancel button stops the transfer, and the row turns green with a done state at the end.
An image field that shows a thumbnail grid of chosen pictures before upload, useful for product photos. Previews use object URLs so nothing leaves the device, and each thumbnail's alt text is the file name for a basic description.
document.getElementById('fui-i').addEventListener('change', e => {
const g = document.getElementById('fui-g'); g.innerHTML = '';
[...e.target.files].forEach(f => { const im = new Image(); im.alt = f.name; im.src = URL.createObjectURL(f); g.appendChild(im); });
});
06. Compact Attach Button
ChatIcon
A paperclip attach button for chat and email composers. It is an icon label for a hidden file input, with an accessible name of Attach a file, and it shows a small count badge once files are attached.
document.getElementById('att-i').addEventListener('change', e => {
const c = document.getElementById('att-c'), n = e.target.files.length;
c.hidden = !n; c.textContent = n;
});
07. Circular Photo Picker
ProfileUpload
A circular picker for profile photos with a camera icon in the middle and a hover overlay that says Change. Once a picture is chosen it fills the circle; the label text stays available to screen readers in every state.
document.getElementById('cpk-i').addEventListener('change', e => {
const f = e.target.files[0]; if (!f) return;
const img = document.getElementById('cpk-img'); img.src = URL.createObjectURL(f); img.hidden = false;
const l = document.querySelector('.cpk'); l.classList.add('has'); l.querySelector('.ov').lastChild.textContent = 'Change photo';
});
08. Upload with Type and Size Check
ValidationUpload
A CV upload that checks the file type and size before accepting it and shows a clear error otherwise. The accept attribute filters the picker, but the script still checks, because accept is only a hint; errors use role="alert".
View code
<div class="fuv">
<label for="fuv-i">Upload your CV <small>(PDF or DOCX, max 2 MB)</small></label>
<input type="file" id="fuv-i" accept=".pdf,.docx" aria-describedby="fuv-m">
<p id="fuv-m"></p>
</div>
const fv = document.getElementById('fuv-i'), fm = document.getElementById('fuv-m');
fv.addEventListener('change', () => {
const f = fv.files[0]; if (!f) return;
const okType = /\.(pdf|docx)$/i.test(f.name), okSize = f.size <= 2 * 1024 * 1024;
if (okType && okSize) { fm.className = 'ok'; fm.removeAttribute('role'); fm.textContent = f.name + ' is ready to send.'; }
else { fm.className = 'err'; fm.setAttribute('role', 'alert'); fm.textContent = !okType ? 'That file type is not allowed. Choose a PDF or DOCX.' : 'That file is larger than 2 MB.'; fv.value = ''; }
});
09. Native Input with file-selector-button
NativePure CSS
The simplest custom upload: keep the native file input and restyle only its button with the ::file-selector-button pseudo-element. You get full browser behaviour, the file name display and keyboard support with a few lines of CSS.
Every design here keeps <input type="file"> in the page, visually hidden but focusable, and styles its label instead. That preserves the file picker, keyboard access and mobile camera options.
The quickest restyle
::file-selector-button styles the native button directly, so a few lines of CSS give a branded upload control with no hidden inputs at all.
Drag and drop is extra
A drop zone should also be clickable and keyboard reachable, because many people cannot drag. Handle dropped files and chosen files with the same function.
Check files yourself
The accept attribute only filters the picker. Check type and size in your script and again on the server, and show clear errors with role="alert".
Frequently asked questions
Are files uploaded anywhere in these demos?
No. Previews use object URLs on your own device, and the progress demo simulates the transfer.
Can I allow several files?
Yes. Add the multiple attribute and read input.files as a list.
How do I remove one file from the selection?
Rebuild the list with a DataTransfer object, as the file list demo does.