$ cat docs/file-upload.md
updated Recently · 20 min read · published
File Upload Introduction
File upload components bridge the browser file picker with your UI. This guide covers production-ready patterns in plain HTML/CSS, Tailwind CSS, and Bootstrap — from a simple click-to-browse zone to drag-and-drop areas, multi-file previews, progress bars, validation, and avatar uploads.
ℹ info
Hide the native input with CSS (or Tailwind's sr-only ) and wrap it with a <label> for a larger, styled hit area. The input stays accessible to keyboards and screen readers.
Basic Upload Zone
A click-to-browse upload zone with a dashed border and inline SVG icon. Clicking the zone opens the native file picker. The <input type="file"> is visually hidden but remains accessible. The zone highlights on drag over for visual feedback.
basic-upload HTML CSS JS Tailwind Bootstrap Live
Copy 1 <div class="upload-zone" id="upload-zone-1"> 2 <input type="file" class="file-input" id="file-1" multiple/> 3 <label for="file-1" class="upload-label"> 4 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" width="32" height="32"> 5 <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/> 6 <polyline points="17 8 12 3 7 8"/> 7 <line x1="12" y1="3" x2="12" y2="15"/> 8 </svg> 9 <span class="upload-title"> 10 Drop files here 11 </span> 12 <span class="upload-sub"> 13 or click to browse · Max 10MB per file 14 </span> 15 </label> 16 </div>
Copy 1 .upload-zone { 2 max-width:380px; 3 margin:16px auto; 4 font-family:system-ui, 5 sans-serif 6 } 7 .file-input { 8 position:absolute; 9 width:1px; 10 height:1px; 11 padding:0; 12 margin:-1px; 13 overflow:hidden; 14 clip:rect(0, 15 0, 16 0, 17 0); 18 border:0 19 } 20 .upload-label { 21 display:flex; 22 flex-direction:column; 23 align-items:center; 24 gap:10px; 25 padding:32px 24px; 26 border:2px dashed #2A2A3E; 27 border-radius:12px; 28 cursor:pointer; 29 transition:all .2s; 30 background:#0A0A0A 31 } 32 .upload-label:hover { 33 border-color:#00FF41; 34 background:rgba(0, 35 255, 36 65, 37 .03) 38 } 39 .upload-label:hover svg { 40 stroke:#00FF41 41 } 42 .upload-label svg { 43 stroke:#555; 44 transition:stroke .2s 45 } 46 .upload-title { 47 font-size:13px; 48 font-weight:600; 49 color:#E0E0E0 50 } 51 .upload-sub { 52 font-size:11px; 53 color:#666 54 } 55 .upload-zone.dragover .upload-label { 56 border-color:#00FF41; 57 background:rgba(0, 58 255, 59 65, 60 .05); 61 transform:scale(1.01) 62 } 63 .upload-zone.dragover .upload-label svg { 64 stroke:#00FF41 65 }
untitled.js wrap JavaScript
Copy 1 const zone=document.getElementById('upload-zone-1');const input=document.getElementById('file-1');['dragenter','dragover'].forEach(e=>zone.addEventListener(e,ev=>{ev.preventDefault();zone.classList.add('dragover')}));['dragleave','drop'].forEach(e=>zone.addEventListener(e,ev=>{ev.preventDefault();zone.classList.remove('dragover')}));zone.addEventListener('drop',ev=>{const files=ev.dataTransfer.files;if(files.length){alert('Selected '+files.length+' file(s): '+Array.from(files).map(f=>f.name).join(', '))}});input.addEventListener('change',()=>{if(input.files.length){alert('Selected '+input.files.length+' file(s): '+Array.from(input.files).map(f=>f.name).join(', '))}})
Copy 1 <div class="min-h-screen flex items-center justify-center bg-[#0A0A0A] p-6"> 2 <div class="w-full max-w-sm group"> 3 <input id="tw-basic" type="file" multiple class="sr-only"/> 4 <label for="tw-basic" class="flex flex-col items-center gap-3 p-8 rounded-xl border-2 border-dashed border-[#2A2A3E] bg-[#0A0A0A] cursor-pointer transition hover:border-[#00FF41] hover:bg-[rgba(0,255,65,0.03)]"> 5 <svg class="w-8 h-8 text-[#555] transition group-hover:text-[#00FF41]" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"> 6 <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/> 7 <polyline points="17 8 12 3 7 8"/> 8 <line x1="12" y1="3" x2="12" y2="15"/> 9 </svg> 10 <span class="text-[13px] font-medium text-[#E0E0E0]"> 11 Drop files here 12 </span> 13 <span class="text-[11px] text-[#666]"> 14 or click to browse · Max 10MB per file 15 </span> 16 </label> 17 </div> 18 </div>
Copy 1 <div class="d-flex align-items-center justify-content-center min-vh-100 p-4"> 2 <style> 3 .bs-zone{max-width:380px;width:100%;font-family:system-ui,sans-serif}.bs-zone label{display:flex;flex-direction:column;align-items:center;gap:10px;padding:32px 24px;border:2px dashed #2A2A3E;border-radius:12px;cursor:pointer;transition:all .2s;background:#0A0A0A}.bs-zone label:hover{border-color:#00FF41;background:rgba(0,255,65,.03)}.bs-zone label:hover svg{stroke:#00FF41}.bs-zone svg{stroke:#555;transition:stroke .2s}.bs-title{font-size:13px;font-weight:600;color:#E0E0E0}.bs-sub{font-size:11px;color:#666} 4 </style> 5 <div class="bs-zone"> 6 <input type="file" id="bs-basic" multiple class="d-none"/> 7 <label for="bs-basic"> 8 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" width="32" height="32"> 9 <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/> 10 <polyline points="17 8 12 3 7 8"/> 11 <line x1="12" y1="3" x2="12" y2="15"/> 12 </svg> 13 <span class="bs-title"> 14 Drop files here 15 </span> 16 <span class="bs-sub"> 17 or click to browse · Max 10MB per file 18 </span> 19 </label> 20 </div> 21 </div>
preview
Drag & Drop Events
The drag-and-drop API fires a sequence of events on the drop zone:dragenter , dragover , dragleave , and drop . Both dragenter and dragover must call preventDefault() to allow dropping.
drag-drop-events.js wrap JavaScript
Copy 1 // Highlight on drag enter/over 2 ['dragenter', 'dragover'].forEach(evt => { 3 zone.addEventListener(evt, (e) => { 4 e.preventDefault(); 5 zone.classList.add('dragover'); 6 }); 7 }); 8 9 // Remove highlight on drag leave/drop 10 ['dragleave', 'drop'].forEach(evt => { 11 zone.addEventListener(evt, (e) => { 12 e.preventDefault(); 13 zone.classList.remove('dragover'); 14 }); 15 }); 16 17 // Handle dropped files 18 zone.addEventListener('drop', (e) => { 19 const files = e.dataTransfer.files; 20 handleFiles(files); 21 });
Multi-File with Previews
A multi-file upload zone that shows thumbnails for images and file type icons for other files. Each file displays its name, size, a progress bar, and a remove button. The footer shows the total file count and combined size.
multi-preview HTML CSS JS Tailwind Bootstrap Live
Copy 1 <div class="mp-upload"> 2 <div class="mp-zone" id="mp-zone"> 3 <input type="file" class="file-input" id="mp-input" multiple/> 4 <label for="mp-input" class="mp-label"> 5 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" width="24" height="24"> 6 <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/> 7 <polyline points="17 8 12 3 7 8"/> 8 <line x1="12" y1="3" x2="12" y2="15"/> 9 </svg> 10 <span> 11 Add files or drop here 12 </span> 13 </label> 14 </div> 15 <div class="mp-list"> 16 <div class="mp-item"> 17 <div class="mp-thumb img" style="background:linear-gradient(135deg,#1a1a2e,#2d1b69)"> 18 <span> 19 IMG 20 </span> 21 </div> 22 <div class="mp-info"> 23 <span class="mp-name"> 24 hero-banner.png 25 </span> 26 <span class="mp-size"> 27 2.4 MB 28 </span> 29 </div> 30 <div class="mp-progress"> 31 <div class="mp-bar" style="width:100%"> 32 </div> 33 </div> 34 <button class="mp-remove" aria-label="Remove file"> 35 ✕ 36 </button> 37 </div> 38 <div class="mp-item"> 39 <div class="mp-thumb img" style="background:linear-gradient(135deg,#0f2027,#203a43)"> 40 <span> 41 IMG 42 </span> 43 </div> 44 <div class="mp-info"> 45 <span class="mp-name"> 46 team-photo.jpg 47 </span> 48 <span class="mp-size"> 49 1.8 MB 50 </span> 51 </div> 52 <div class="mp-progress"> 53 <div class="mp-bar" style="width:100%"> 54 </div> 55 </div> 56 <button class="mp-remove" aria-label="Remove file"> 57 ✕ 58 </button> 59 </div> 60 <div class="mp-item"> 61 <div class="mp-thumb doc"> 62 <span> 63 PDF 64 </span> 65 </div> 66 <div class="mp-info"> 67 <span class="mp-name"> 68 project-spec.pdf 69 </span> 70 <span class="mp-size"> 71 840 KB 72 </span> 73 </div> 74 <div class="mp-progress"> 75 <div class="mp-bar" style="width:65%"> 76 </div> 77 </div> 78 <button class="mp-remove" aria-label="Remove file"> 79 ✕ 80 </button> 81 </div> 82 </div> 83 <div class="mp-footer"> 84 <span class="mp-count"> 85 3 files selected 86 </span> 87 <span class="mp-total"> 88 Total: 5.0 MB 89 </span> 90 </div> 91 </div>
Copy 1 .mp-upload { 2 max-width:380px; 3 margin:16px auto; 4 font-family:system-ui, 5 sans-serif 6 } 7 .mp-zone { 8 margin-bottom:12px 9 } 10 .file-input { 11 position:absolute; 12 width:1px; 13 height:1px; 14 padding:0; 15 margin:-1px; 16 overflow:hidden; 17 clip:rect(0, 18 0, 19 0, 20 0); 21 border:0 22 } 23 .mp-label { 24 display:flex; 25 align-items:center; 26 gap:8px; 27 justify-content:center; 28 padding:14px; 29 border:2px dashed #2A2A3E; 30 border-radius:10px; 31 cursor:pointer; 32 transition:all .2s; 33 font-size:12px; 34 color:#666; 35 background:#0A0A0A 36 } 37 .mp-label:hover { 38 border-color:#00FF41; 39 color:#00FF41; 40 background:rgba(0, 41 255, 42 65, 43 .03) 44 } 45 .mp-list { 46 display:flex; 47 flex-direction:column; 48 gap:6px 49 } 50 .mp-item { 51 display:flex; 52 align-items:center; 53 gap:10px; 54 padding:10px 12px; 55 background:#111; 56 border:1px solid #222; 57 border-radius:8px; 58 position:relative 59 } 60 .mp-thumb { 61 width:36px; 62 height:36px; 63 border-radius:6px; 64 display:flex; 65 align-items:center; 66 justify-content:center; 67 font-size:8px; 68 font-weight:800; 69 color:rgba(255, 70 255, 71 255, 72 .5); 73 flex-shrink:0 74 } 75 .mp-thumb.doc { 76 background:#1a1a2e; 77 color:#EF4444 78 } 79 .mp-info { 80 flex:1; 81 min-width:0; 82 display:flex; 83 flex-direction:column; 84 gap:2px 85 } 86 .mp-name { 87 font-size:12px; 88 color:#E0E0E0; 89 font-weight:500; 90 white-space:nowrap; 91 overflow:hidden; 92 text-overflow:ellipsis 93 } 94 .mp-size { 95 font-size:10px; 96 color:#666 97 } 98 .mp-progress { 99 position:absolute; 100 bottom:0; 101 left:12px; 102 right:40px; 103 height:2px; 104 background:#1A1A2E; 105 border-radius:1px 106 } 107 .mp-bar { 108 height:100%; 109 background:linear-gradient(90deg, 110 #00FF41, 111 #00CC33); 112 border-radius:1px; 113 transition:width .3s ease 114 } 115 .mp-remove { 116 width:24px; 117 height:24px; 118 border-radius:6px; 119 border:none; 120 background:transparent; 121 color:#555; 122 cursor:pointer; 123 display:flex; 124 align-items:center; 125 justify-content:center; 126 font-size:11px; 127 transition:all .2s; 128 flex-shrink:0 129 } 130 .mp-remove:hover { 131 background:rgba(239, 132 68, 133 68, 134 .1); 135 color:#EF4444 136 } 137 .mp-footer { 138 display:flex; 139 justify-content:space-between; 140 margin-top:10px; 141 padding:0 4px 142 } 143 .mp-count, 144 .mp-total { 145 font-size:10px; 146 color:#666 147 } 148 .mp-total { 149 color:#00FF41; 150 font-weight:600 151 }
untitled.js wrap JavaScript
Copy 1 document.querySelectorAll('.mp-remove').forEach(btn=>{btn.addEventListener('click',()=>{const item=btn.closest('.mp-item');item.style.opacity='0';item.style.transform='translateX(10px)';item.style.transition='all .2s';setTimeout(()=>item.remove(),200)})})
Copy 1 <div class="min-h-screen bg-[#0A0A0A] p-6"> 2 <div class="max-w-sm mx-auto space-y-3"> 3 <label for="tw-multi" class="flex items-center justify-center gap-2 p-4 rounded-xl border-2 border-dashed border-[#2A2A3E] bg-[#0A0A0A] text-[#808080] cursor-pointer transition hover:border-[#00FF41] hover:text-[#00FF41] hover:bg-[rgba(0,255,65,0.03)]"> 4 <svg class="w-5 h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"> 5 <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/> 6 <polyline points="17 8 12 3 7 8"/> 7 <line x1="12" y1="3" x2="12" y2="15"/> 8 </svg> 9 <span class="text-xs font-medium"> 10 Add files or drop here 11 </span> 12 </label> 13 <input id="tw-multi" type="file" multiple class="sr-only"/> 14 <div class="space-y-2"> 15 <div class="flex items-center gap-3 p-3 rounded-lg bg-[#111] border border-[#222] relative"> 16 <div class="w-9 h-9 rounded-md flex items-center justify-center text-[8px] font-extrabold text-white/50 bg-gradient-to-br from-[#1a1a2e] to-[#2d1b69]"> 17 IMG 18 </div> 19 <div class="flex-1 min-w-0 flex flex-col gap-0.5"> 20 <span class="text-xs font-medium text-[#E0E0E0] truncate"> 21 hero-banner.png 22 </span> 23 <span class="text-[10px] text-[#666]"> 24 2.4 MB 25 </span> 26 </div> 27 <button aria-label="Remove file" class="w-6 h-6 rounded-md flex items-center justify-center text-[11px] text-[#555] hover:bg-[rgba(239,68,68,0.1)] hover:text-[#EF4444] transition"> 28 ✕ 29 </button> 30 <div class="absolute bottom-0 left-3 right-10 h-0.5 bg-[#1A1A2E] rounded-sm"> 31 <div class="h-full bg-gradient-to-r from-[#00FF41] to-[#00CC33] rounded-sm" style="width:100%"> 32 </div> 33 </div> 34 </div> 35 <div class="flex items-center gap-3 p-3 rounded-lg bg-[#111] border border-[#222] relative"> 36 <div class="w-9 h-9 rounded-md flex items-center justify-center text-[8px] font-extrabold text-white/50 bg-gradient-to-br from-[#0f2027] to-[#203a43]"> 37 IMG 38 </div> 39 <div class="flex-1 min-w-0 flex flex-col gap-0.5"> 40 <span class="text-xs font-medium text-[#E0E0E0] truncate"> 41 team-photo.jpg 42 </span> 43 <span class="text-[10px] text-[#666]"> 44 1.8 MB 45 </span> 46 </div> 47 <button aria-label="Remove file" class="w-6 h-6 rounded-md flex items-center justify-center text-[11px] text-[#555] hover:bg-[rgba(239,68,68,0.1)] hover:text-[#EF4444] transition"> 48 ✕ 49 </button> 50 <div class="absolute bottom-0 left-3 right-10 h-0.5 bg-[#1A1A2E] rounded-sm"> 51 <div class="h-full bg-gradient-to-r from-[#00FF41] to-[#00CC33] rounded-sm" style="width:100%"> 52 </div> 53 </div> 54 </div> 55 <div class="flex items-center gap-3 p-3 rounded-lg bg-[#111] border border-[#222] relative"> 56 <div class="w-9 h-9 rounded-md flex items-center justify-center text-[8px] font-extrabold text-[#EF4444] bg-[#1A1A2E]"> 57 PDF 58 </div> 59 <div class="flex-1 min-w-0 flex flex-col gap-0.5"> 60 <span class="text-xs font-medium text-[#E0E0E0] truncate"> 61 project-spec.pdf 62 </span> 63 <span class="text-[10px] text-[#666]"> 64 840 KB 65 </span> 66 </div> 67 <button aria-label="Remove file" class="w-6 h-6 rounded-md flex items-center justify-center text-[11px] text-[#555] hover:bg-[rgba(239,68,68,0.1)] hover:text-[#EF4444] transition"> 68 ✕ 69 </button> 70 <div class="absolute bottom-0 left-3 right-10 h-0.5 bg-[#1A1A2E] rounded-sm"> 71 <div class="h-full bg-gradient-to-r from-[#00FF41] to-[#00CC33] rounded-sm" style="width:65%"> 72 </div> 73 </div> 74 </div> 75 </div> 76 <div class="flex justify-between text-[10px] text-[#666] px-1"> 77 <span> 78 3 files selected 79 </span> 80 <span class="text-[#00FF41] font-semibold"> 81 Total: 5.0 MB 82 </span> 83 </div> 84 </div> 85 </div>
Copy 1 <div class="min-vh-100 d-flex align-items-center justify-content-center p-4"> 2 <style> 3 .bs-mp{max-width:380px;width:100%;font-family:system-ui,sans-serif}.bs-zone label{display:flex;align-items:center;gap:8px;justify-content:center;padding:14px;border:2px dashed #2A2A3E;border-radius:10px;cursor:pointer;transition:all .2s;font-size:12px;color:#666;background:#0A0A0A}.bs-zone label:hover{border-color:#00FF41;color:#00FF41;background:rgba(0,255,65,.03)}.bs-item{display:flex;align-items:center;gap:10px;padding:10px 12px;background:#111;border:1px solid #222;border-radius:8px;position:relative}.bs-thumb{width:36px;height:36px;border-radius:6px;display:flex;align-items:center;justify-content:center;font-size:8px;font-weight:800;color:rgba(255,255,255,.5);flex-shrink:0}.bs-name{font-size:12px;color:#E0E0E0;font-weight:500;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.bs-size{font-size:10px;color:#666}.bs-progress{position:absolute;bottom:0;left:12px;right:40px;height:2px;background:#1A1A2E;border-radius:1px}.bs-bar{height:100%;background:linear-gradient(90deg,#00FF41,#00CC33);border-radius:1px}.bs-remove{width:24px;height:24px;border-radius:6px;border:none;background:transparent;color:#555;cursor:pointer;display:flex;align-items:center;justify-content:center;font-size:11px;transition:all .2s;flex-shrink:0}.bs-remove:hover{background:rgba(239,68,68,.1);color:#EF4444}.bs-footer{display:flex;justify-content:space-between;margin-top:10px;padding:0 4px;font-size:10px;color:#666}.bs-total{color:#00FF41;font-weight:600} 4 </style> 5 <div class="bs-mp"> 6 <div class="bs-zone mb-3"> 7 <input type="file" id="bs-multi" multiple class="d-none"/> 8 <label for="bs-multi"> 9 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" width="24" height="24"> 10 <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/> 11 <polyline points="17 8 12 3 7 8"/> 12 <line x1="12" y1="3" x2="12" y2="15"/> 13 </svg> 14 <span> 15 Add files or drop here 16 </span> 17 </label> 18 </div> 19 <div class="d-flex flex-column gap-2"> 20 <div class="bs-item"> 21 <div class="bs-thumb" style="background:linear-gradient(135deg,#1a1a2e,#2d1b69)"> 22 IMG 23 </div> 24 <div class="flex-grow-1 d-flex flex-column" style="min-width:0"> 25 <span class="bs-name"> 26 hero-banner.png 27 </span> 28 <span class="bs-size"> 29 2.4 MB 30 </span> 31 </div> 32 <button class="bs-remove" aria-label="Remove file"> 33 ✕ 34 </button> 35 <div class="bs-progress"> 36 <div class="bs-bar" style="width:100%"> 37 </div> 38 </div> 39 </div> 40 <div class="bs-item"> 41 <div class="bs-thumb" style="background:linear-gradient(135deg,#0f2027,#203a43)"> 42 IMG 43 </div> 44 <div class="flex-grow-1 d-flex flex-column" style="min-width:0"> 45 <span class="bs-name"> 46 team-photo.jpg 47 </span> 48 <span class="bs-size"> 49 1.8 MB 50 </span> 51 </div> 52 <button class="bs-remove" aria-label="Remove file"> 53 ✕ 54 </button> 55 <div class="bs-progress"> 56 <div class="bs-bar" style="width:100%"> 57 </div> 58 </div> 59 </div> 60 <div class="bs-item"> 61 <div class="bs-thumb" style="background:#1A1A2E;color:#EF4444"> 62 PDF 63 </div> 64 <div class="flex-grow-1 d-flex flex-column" style="min-width:0"> 65 <span class="bs-name"> 66 project-spec.pdf 67 </span> 68 <span class="bs-size"> 69 840 KB 70 </span> 71 </div> 72 <button class="bs-remove" aria-label="Remove file"> 73 ✕ 74 </button> 75 <div class="bs-progress"> 76 <div class="bs-bar" style="width:65%"> 77 </div> 78 </div> 79 </div> 80 </div> 81 <div class="bs-footer"> 82 <span> 83 3 files selected 84 </span> 85 <span class="bs-total"> 86 Total: 5.0 MB 87 </span> 88 </div> 89 </div> 90 </div>
preview
Upload Progress
Files with animated progress bars showing upload status. Each bar displays the percentage complete with a shimmer animation during upload. Completed files show a checkmark with a green background, while queued files display a clock icon.
upload-progress HTML CSS Tailwind Bootstrap Live
Copy 1 <div class="prog-upload"> 2 <div class="pu-item done"> 3 <div class="pu-icon done"> 4 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" width="14" height="14"> 5 <polyline points="20 6 9 17 4 12"/> 6 </svg> 7 </div> 8 <div class="pu-details"> 9 <div class="pu-row"> 10 <span class="pu-name"> 11 design-system.zip 12 </span> 13 <span class="pu-status done"> 14 Complete 15 </span> 16 </div> 17 <div class="pu-bar-wrap"> 18 <div class="pu-bar done" style="width:100%"> 19 </div> 20 </div> 21 <span class="pu-meta"> 22 4.2 MB · 12s 23 </span> 24 </div> 25 </div> 26 <div class="pu-item active"> 27 <div class="pu-icon active"> 28 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="14" height="14"> 29 <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/> 30 <polyline points="17 8 12 3 7 8"/> 31 <line x1="12" y1="3" x2="12" y2="15"/> 32 </svg> 33 </div> 34 <div class="pu-details"> 35 <div class="pu-row"> 36 <span class="pu-name"> 37 assets-bundle.tar.gz 38 </span> 39 <span class="pu-status active"> 40 72% 41 </span> 42 </div> 43 <div class="pu-bar-wrap"> 44 <div class="pu-bar active" style="width:72%"> 45 </div> 46 </div> 47 <span class="pu-meta"> 48 18.6 MB · Uploading... 49 </span> 50 </div> 51 </div> 52 <div class="pu-item queued"> 53 <div class="pu-icon"> 54 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="14" height="14"> 55 <circle cx="12" cy="12" r="10"/> 56 <polyline points="12 6 12 12 16 14"/> 57 </svg> 58 </div> 59 <div class="pu-details"> 60 <div class="pu-row"> 61 <span class="pu-name"> 62 video-recording.mp4 63 </span> 64 <span class="pu-status"> 65 Queued 66 </span> 67 </div> 68 <div class="pu-bar-wrap"> 69 <div class="pu-bar" style="width:0%"> 70 </div> 71 </div> 72 <span class="pu-meta"> 73 142 MB · Waiting 74 </span> 75 </div> 76 </div> 77 </div>
Copy 1 .prog-upload { 2 max-width:380px; 3 margin:16px auto; 4 display:flex; 5 flex-direction:column; 6 gap:8px; 7 padding:0 4px; 8 font-family:system-ui, 9 sans-serif 10 } 11 .pu-item { 12 display:flex; 13 gap:12px; 14 padding:12px; 15 background:#111; 16 border:1px solid #222; 17 border-radius:8px; 18 align-items:flex-start 19 } 20 .pu-icon { 21 width:28px; 22 height:28px; 23 border-radius:6px; 24 display:flex; 25 align-items:center; 26 justify-content:center; 27 border:1px solid #333; 28 color:#555; 29 background:#0A0A0A; 30 flex-shrink:0 31 } 32 .pu-icon.done { 33 background:#00FF41; 34 border-color:#00FF41; 35 color:#0A0A0A 36 } 37 .pu-icon.active { 38 border-color:#00FF41; 39 color:#00FF41 40 } 41 .pu-details { 42 flex:1; 43 min-width:0; 44 display:flex; 45 flex-direction:column; 46 gap:6px 47 } 48 .pu-row { 49 display:flex; 50 justify-content:space-between; 51 align-items:center 52 } 53 .pu-name { 54 font-size:12px; 55 color:#E0E0E0; 56 font-weight:500; 57 white-space:nowrap; 58 overflow:hidden; 59 text-overflow:ellipsis 60 } 61 .pu-status { 62 font-size:10px; 63 color:#666; 64 font-weight:500 65 } 66 .pu-status.done { 67 color:#00FF41 68 } 69 .pu-status.active { 70 color:#00FF41; 71 font-weight:700; 72 font-variant-numeric:tabular-nums 73 } 74 .pu-bar-wrap { 75 height:3px; 76 background:#1A1A2E; 77 border-radius:2px; 78 overflow:hidden 79 } 80 .pu-bar { 81 height:100%; 82 border-radius:2px; 83 transition:width .5s ease; 84 background:#333 85 } 86 .pu-bar.done { 87 background:#00FF41 88 } 89 .pu-bar.active { 90 background:linear-gradient(90deg, 91 #00FF41, 92 #00CC33); 93 animation:shimmer 1.5s infinite 94 } 95 @keyframes shimmer { 96 0% { 97 opacity:1 98 } 99 50% { 100 opacity:.7 101 } 102 100% { 103 opacity:1 104 } 105 } 106 .pu-meta { 107 font-size:10px; 108 color:#555 109 }
Copy 1 <div class="min-h-screen bg-[#0A0A0A] p-6"> 2 <div class="max-w-sm mx-auto space-y-2"> 3 <div class="flex gap-3 p-3 rounded-lg bg-[#111] border border-[#222] items-start"> 4 <div class="w-7 h-7 rounded-md flex items-center justify-center bg-[#00FF41] text-[#0A0A0A] border border-[#00FF41] flex-shrink-0"> 5 <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"> 6 <polyline points="20 6 9 17 4 12"/> 7 </svg> 8 </div> 9 <div class="flex-1 min-w-0 flex flex-col gap-1.5"> 10 <div class="flex justify-between items-center"> 11 <span class="text-xs font-medium text-[#E0E0E0] truncate"> 12 design-system.zip 13 </span> 14 <span class="text-[10px] font-medium text-[#00FF41]"> 15 Complete 16 </span> 17 </div> 18 <div class="h-[3px] bg-[#1A1A2E] rounded-sm overflow-hidden"> 19 <div class="h-full bg-[#00FF41] rounded-sm" style="width:100%"> 20 </div> 21 </div> 22 <span class="text-[10px] text-[#555]"> 23 4.2 MB · 12s 24 </span> 25 </div> 26 </div> 27 <div class="flex gap-3 p-3 rounded-lg bg-[#111] border border-[#222] items-start"> 28 <div class="w-7 h-7 rounded-md flex items-center justify-center border border-[#00FF41] text-[#00FF41] bg-[#0A0A0A] flex-shrink-0"> 29 <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> 30 <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/> 31 <polyline points="17 8 12 3 7 8"/> 32 <line x1="12" y1="3" x2="12" y2="15"/> 33 </svg> 34 </div> 35 <div class="flex-1 min-w-0 flex flex-col gap-1.5"> 36 <div class="flex justify-between items-center"> 37 <span class="text-xs font-medium text-[#E0E0E0] truncate"> 38 assets-bundle.tar.gz 39 </span> 40 <span class="text-[10px] font-bold text-[#00FF41] tabular-nums"> 41 72% 42 </span> 43 </div> 44 <div class="h-[3px] bg-[#1A1A2E] rounded-sm overflow-hidden"> 45 <div class="h-full bg-gradient-to-r from-[#00FF41] to-[#00CC33] rounded-sm animate-pulse" style="width:72%"> 46 </div> 47 </div> 48 <span class="text-[10px] text-[#555]"> 49 18.6 MB · Uploading... 50 </span> 51 </div> 52 </div> 53 <div class="flex gap-3 p-3 rounded-lg bg-[#111] border border-[#222] items-start"> 54 <div class="w-7 h-7 rounded-md flex items-center justify-center border border-[#333] text-[#555] bg-[#0A0A0A] flex-shrink-0"> 55 <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> 56 <circle cx="12" cy="12" r="10"/> 57 <polyline points="12 6 12 12 16 14"/> 58 </svg> 59 </div> 60 <div class="flex-1 min-w-0 flex flex-col gap-1.5"> 61 <div class="flex justify-between items-center"> 62 <span class="text-xs font-medium text-[#E0E0E0] truncate"> 63 video-recording.mp4 64 </span> 65 <span class="text-[10px] font-medium text-[#666]"> 66 Queued 67 </span> 68 </div> 69 <div class="h-[3px] bg-[#1A1A2E] rounded-sm overflow-hidden"> 70 <div class="h-full bg-[#333] rounded-sm" style="width:0%"> 71 </div> 72 </div> 73 <span class="text-[10px] text-[#555]"> 74 142 MB · Waiting 75 </span> 76 </div> 77 </div> 78 </div> 79 </div>
Copy 1 <div class="min-vh-100 d-flex align-items-center justify-content-center p-4"> 2 <style> 3 .bs-prog{max-width:380px;width:100%;font-family:system-ui,sans-serif}.bs-pu{display:flex;gap:12px;padding:12px;background:#111;border:1px solid #222;border-radius:8px;align-items:flex-start}.bs-icon{width:28px;height:28px;border-radius:6px;display:flex;align-items:center;justify-content:center;border:1px solid #333;color:#555;background:#0A0A0A;flex-shrink:0}.bs-icon.done{background:#00FF41;border-color:#00FF41;color:#0A0A0A}.bs-icon.active{border-color:#00FF41;color:#00FF41}.bs-details{flex:1;min-width:0;display:flex;flex-direction:column;gap:6px}.bs-row{display:flex;justify-content:space-between;align-items:center}.bs-name{font-size:12px;color:#E0E0E0;font-weight:500;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.bs-status{font-size:10px;color:#666;font-weight:500}.bs-status.done{color:#00FF41}.bs-status.active{color:#00FF41;font-weight:700}.bs-bar-wrap{height:3px;background:#1A1A2E;border-radius:2px;overflow:hidden}.bs-bar{height:100%;border-radius:2px;background:#333}.bs-bar.done{background:#00FF41}.bs-bar.active{background:linear-gradient(90deg,#00FF41,#00CC33);animation:shimmer 1.5s infinite}@keyframes shimmer{0%{opacity:1}50%{opacity:.7}100%{opacity:1}}.bs-meta{font-size:10px;color:#555} 4 </style> 5 <div class="bs-prog d-flex flex-column gap-2"> 6 <div class="bs-pu"> 7 <div class="bs-icon done"> 8 <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"> 9 <polyline points="20 6 9 17 4 12"/> 10 </svg> 11 </div> 12 <div class="bs-details"> 13 <div class="bs-row"> 14 <span class="bs-name"> 15 design-system.zip 16 </span> 17 <span class="bs-status done"> 18 Complete 19 </span> 20 </div> 21 <div class="bs-bar-wrap"> 22 <div class="bs-bar done" style="width:100%"> 23 </div> 24 </div> 25 <span class="bs-meta"> 26 4.2 MB · 12s 27 </span> 28 </div> 29 </div> 30 <div class="bs-pu"> 31 <div class="bs-icon active"> 32 <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> 33 <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/> 34 <polyline points="17 8 12 3 7 8"/> 35 <line x1="12" y1="3" x2="12" y2="15"/> 36 </svg> 37 </div> 38 <div class="bs-details"> 39 <div class="bs-row"> 40 <span class="bs-name"> 41 assets-bundle.tar.gz 42 </span> 43 <span class="bs-status active"> 44 72% 45 </span> 46 </div> 47 <div class="bs-bar-wrap"> 48 <div class="bs-bar active" style="width:72%"> 49 </div> 50 </div> 51 <span class="bs-meta"> 52 18.6 MB · Uploading... 53 </span> 54 </div> 55 </div> 56 <div class="bs-pu"> 57 <div class="bs-icon"> 58 <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> 59 <circle cx="12" cy="12" r="10"/> 60 <polyline points="12 6 12 12 16 14"/> 61 </svg> 62 </div> 63 <div class="bs-details"> 64 <div class="bs-row"> 65 <span class="bs-name"> 66 video-recording.mp4 67 </span> 68 <span class="bs-status"> 69 Queued 70 </span> 71 </div> 72 <div class="bs-bar-wrap"> 73 <div class="bs-bar" style="width:0%"> 74 </div> 75 </div> 76 <span class="bs-meta"> 77 142 MB · Waiting 78 </span> 79 </div> 80 </div> 81 </div> 82 </div>
preview
File Validation
Client-side validation that checks file type, size, and count before upload. Invalid files show inline error messages with the specific rejection reason, while valid files proceed to the upload queue with a green checkmark.
validation HTML CSS Tailwind Bootstrap Live
Copy 1 <div class="valid-upload"> 2 <div class="vu-zone" id="vu-zone"> 3 <input type="file" class="file-input" id="vu-input" accept=".jpg,.jpeg,.png,.gif,.pdf,.docx" multiple/> 4 <label for="vu-input" class="vu-label"> 5 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" width="28" height="28"> 6 <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/> 7 <polyline points="17 8 12 3 7 8"/> 8 <line x1="12" y1="3" x2="12" y2="15"/> 9 </svg> 10 <span> 11 Drop files here 12 </span> 13 <span class="vu-hint"> 14 JPG, PNG, GIF, PDF, DOCX · Max 5MB · Max 5 files 15 </span> 16 </label> 17 </div> 18 <div class="vu-list"> 19 <div class="vu-item valid"> 20 <div class="vi-icon valid"> 21 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" width="12" height="12"> 22 <polyline points="20 6 9 17 4 12"/> 23 </svg> 24 </div> 25 <div class="vi-info"> 26 <span class="vi-name"> 27 photo.jpg 28 </span> 29 <span class="vi-detail"> 30 1.2 MB · Image 31 </span> 32 </div> 33 </div> 34 <div class="vu-item invalid"> 35 <div class="vi-icon invalid"> 36 ✕ 37 </div> 38 <div class="vi-info"> 39 <span class="vi-name"> 40 video.mp4 41 </span> 42 <span class="vi-detail invalid"> 43 File type not allowed · MP4 44 </span> 45 </div> 46 </div> 47 <div class="vu-item invalid"> 48 <div class="vi-icon invalid"> 49 ✕ 50 </div> 51 <div class="vi-info"> 52 <span class="vi-name"> 53 large-file.zip 54 </span> 55 <span class="vi-detail invalid"> 56 Exceeds 5MB limit · 24 MB 57 </span> 58 </div> 59 </div> 60 </div> 61 </div>
Copy 1 .valid-upload { 2 max-width:380px; 3 margin:16px auto; 4 font-family:system-ui, 5 sans-serif 6 } 7 .vu-zone { 8 margin-bottom:12px 9 } 10 .file-input { 11 position:absolute; 12 width:1px; 13 height:1px; 14 padding:0; 15 margin:-1px; 16 overflow:hidden; 17 clip:rect(0, 18 0, 19 0, 20 0); 21 border:0 22 } 23 .vu-label { 24 display:flex; 25 flex-direction:column; 26 align-items:center; 27 gap:8px; 28 padding:24px; 29 border:2px dashed #2A2A3E; 30 border-radius:10px; 31 cursor:pointer; 32 transition:all .2s; 33 background:#0A0A0A 34 } 35 .vu-label:hover { 36 border-color:#00FF41; 37 background:rgba(0, 38 255, 39 65, 40 .03) 41 } 42 .vu-label svg { 43 stroke:#555; 44 transition:stroke .2s 45 } 46 .vu-label:hover svg { 47 stroke:#00FF41 48 } 49 .vu-label span { 50 font-size:12px; 51 color:#E0E0E0; 52 font-weight:500 53 } 54 .vu-hint { 55 font-size:10px!important; 56 color:#666!important; 57 font-weight:400!important 58 } 59 .vu-list { 60 display:flex; 61 flex-direction:column; 62 gap:6px 63 } 64 .vu-item { 65 display:flex; 66 gap:10px; 67 padding:10px 12px; 68 border-radius:8px; 69 border:1px solid #222; 70 background:#111; 71 align-items:center 72 } 73 .vu-item.invalid { 74 border-color:rgba(239, 75 68, 76 68, 77 .2); 78 background:rgba(239, 79 68, 80 68, 81 .03) 82 } 83 .vi-icon { 84 width:24px; 85 height:24px; 86 border-radius:50%; 87 display:flex; 88 align-items:center; 89 justify-content:center; 90 font-size:10px; 91 font-weight:700; 92 flex-shrink:0 93 } 94 .vi-icon.valid { 95 background:rgba(0, 96 255, 97 65, 98 .1); 99 color:#00FF41 100 } 101 .vi-icon.invalid { 102 background:rgba(239, 103 68, 104 68, 105 .1); 106 color:#EF4444; 107 font-size:11px 108 } 109 .vi-info { 110 display:flex; 111 flex-direction:column; 112 gap:2px 113 } 114 .vi-name { 115 font-size:12px; 116 color:#E0E0E0; 117 font-weight:500 118 } 119 .vi-detail { 120 font-size:10px; 121 color:#666 122 } 123 .vi-detail.invalid { 124 color:#EF4444 125 }
Copy 1 <div class="min-h-screen bg-[#0A0A0A] p-6"> 2 <div class="max-w-sm mx-auto space-y-3"> 3 <label for="tw-valid" class="flex flex-col items-center gap-2 p-6 rounded-xl border-2 border-dashed border-[#2A2A3E] bg-[#0A0A0A] cursor-pointer transition hover:border-[#00FF41] hover:bg-[rgba(0,255,65,0.03)]"> 4 <svg class="w-7 h-7 text-[#555]" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"> 5 <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/> 6 <polyline points="17 8 12 3 7 8"/> 7 <line x1="12" y1="3" x2="12" y2="15"/> 8 </svg> 9 <span class="text-xs font-medium text-[#E0E0E0]"> 10 Drop files here 11 </span> 12 <span class="text-[10px] text-[#666]"> 13 JPG, PNG, GIF, PDF, DOCX · Max 5MB · Max 5 files 14 </span> 15 </label> 16 <input id="tw-valid" type="file" multiple accept=".jpg,.jpeg,.png,.gif,.pdf,.docx" class="sr-only"/> 17 <div class="space-y-2"> 18 <div class="flex items-center gap-3 p-3 rounded-lg bg-[#111] border border-[#222]"> 19 <div class="w-6 h-6 rounded-full flex items-center justify-center text-[10px] font-bold bg-[rgba(0,255,65,0.1)] text-[#00FF41]"> 20 <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"> 21 <polyline points="20 6 9 17 4 12"/> 22 </svg> 23 </div> 24 <div class="flex flex-col"> 25 <span class="text-xs font-medium text-[#E0E0E0]"> 26 photo.jpg 27 </span> 28 <span class="text-[10px] text-[#666]"> 29 1.2 MB · Image 30 </span> 31 </div> 32 </div> 33 <div class="flex items-center gap-3 p-3 rounded-lg bg-[rgba(239,68,68,0.03)] border border-[rgba(239,68,68,0.2)]"> 34 <div class="w-6 h-6 rounded-full flex items-center justify-center text-[10px] font-bold bg-[rgba(239,68,68,0.1)] text-[#EF4444]"> 35 ✕ 36 </div> 37 <div class="flex flex-col"> 38 <span class="text-xs font-medium text-[#E0E0E0]"> 39 video.mp4 40 </span> 41 <span class="text-[10px] text-[#EF4444]"> 42 File type not allowed · MP4 43 </span> 44 </div> 45 </div> 46 <div class="flex items-center gap-3 p-3 rounded-lg bg-[rgba(239,68,68,0.03)] border border-[rgba(239,68,68,0.2)]"> 47 <div class="w-6 h-6 rounded-full flex items-center justify-center text-[10px] font-bold bg-[rgba(239,68,68,0.1)] text-[#EF4444]"> 48 ✕ 49 </div> 50 <div class="flex flex-col"> 51 <span class="text-xs font-medium text-[#E0E0E0]"> 52 large-file.zip 53 </span> 54 <span class="text-[10px] text-[#EF4444]"> 55 Exceeds 5MB limit · 24 MB 56 </span> 57 </div> 58 </div> 59 </div> 60 </div> 61 </div>
Copy 1 <div class="min-vh-100 d-flex align-items-center justify-content-center p-4"> 2 <style> 3 .bs-valid{max-width:380px;width:100%;font-family:system-ui,sans-serif}.bs-zone label{display:flex;flex-direction:column;align-items:center;gap:8px;padding:24px;border:2px dashed #2A2A3E;border-radius:10px;cursor:pointer;transition:all .2s;background:#0A0A0A}.bs-zone label:hover{border-color:#00FF41;background:rgba(0,255,65,.03)}.bs-zone svg{stroke:#555;transition:stroke .2s}.bs-zone label:hover svg{stroke:#00FF41}.bs-item{display:flex;gap:10px;padding:10px 12px;border-radius:8px;border:1px solid #222;background:#111;align-items:center}.bs-item.invalid{border-color:rgba(239,68,68,.2);background:rgba(239,68,68,.03)}.vi-icon{width:24px;height:24px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:10px;font-weight:700;flex-shrink:0}.vi-icon.valid{background:rgba(0,255,65,.1);color:#00FF41}.vi-icon.invalid{background:rgba(239,68,68,.1);color:#EF4444;font-size:11px}.vi-name{font-size:12px;color:#E0E0E0;font-weight:500}.vi-detail{font-size:10px;color:#666}.vi-detail.invalid{color:#EF4444} 4 </style> 5 <div class="bs-valid"> 6 <div class="bs-zone mb-3"> 7 <input type="file" id="bs-valid" accept=".jpg,.jpeg,.png,.gif,.pdf,.docx" multiple class="d-none"/> 8 <label for="bs-valid"> 9 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" width="28" height="28"> 10 <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/> 11 <polyline points="17 8 12 3 7 8"/> 12 <line x1="12" y1="3" x2="12" y2="15"/> 13 </svg> 14 <span class="text-light fw-medium"> 15 Drop files here 16 </span> 17 <small class="text-secondary"> 18 JPG, PNG, GIF, PDF, DOCX · Max 5MB · Max 5 files 19 </small> 20 </label> 21 </div> 22 <div class="d-flex flex-column gap-2"> 23 <div class="bs-item"> 24 <div class="vi-icon valid"> 25 <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"> 26 <polyline points="20 6 9 17 4 12"/> 27 </svg> 28 </div> 29 <div class="d-flex flex-column"> 30 <span class="vi-name"> 31 photo.jpg 32 </span> 33 <span class="vi-detail"> 34 1.2 MB · Image 35 </span> 36 </div> 37 </div> 38 <div class="bs-item invalid"> 39 <div class="vi-icon invalid"> 40 ✕ 41 </div> 42 <div class="d-flex flex-column"> 43 <span class="vi-name"> 44 video.mp4 45 </span> 46 <span class="vi-detail invalid"> 47 File type not allowed · MP4 48 </span> 49 </div> 50 </div> 51 <div class="bs-item invalid"> 52 <div class="vi-icon invalid"> 53 ✕ 54 </div> 55 <div class="d-flex flex-column"> 56 <span class="vi-name"> 57 large-file.zip 58 </span> 59 <span class="vi-detail invalid"> 60 Exceeds 5MB limit · 24 MB 61 </span> 62 </div> 63 </div> 64 </div> 65 </div> 66 </div>
preview
Avatar Upload
A circular avatar upload with image preview. Clicking the avatar or button opens the file picker filtered to images. The selected image appears as a circular preview using FileReader.readAsDataURL() .
avatar-upload HTML CSS JS Tailwind Bootstrap Live
Copy 1 <div class="avatar-upload"> 2 <div class="av-wrap"> 3 <div class="av-preview" id="av-preview"> 4 <div class="av-placeholder"> 5 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" width="28" height="28"> 6 <path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/> 7 <circle cx="12" cy="7" r="4"/> 8 </svg> 9 </div> 10 </div> 11 <input type="file" class="file-input" id="av-input" accept="image/*"/> 12 <label for="av-input" class="av-btn"> 13 Change Photo 14 </label> 15 <span class="av-hint"> 16 JPG or PNG · Max 2MB 17 </span> 18 </div> 19 </div>
Copy 1 .avatar-upload { 2 display:flex; 3 flex-direction:column; 4 align-items:center; 5 padding:24px; 6 font-family:system-ui, 7 sans-serif 8 } 9 .av-wrap { 10 display:flex; 11 flex-direction:column; 12 align-items:center; 13 gap:12px 14 } 15 .av-preview { 16 width:80px; 17 height:80px; 18 border-radius:50%; 19 background:#1A1A2E; 20 border:2px dashed #333; 21 display:flex; 22 align-items:center; 23 justify-content:center; 24 overflow:hidden; 25 transition:border-color .2s; 26 cursor:pointer 27 } 28 .av-preview:hover { 29 border-color:#00FF41 30 } 31 .av-preview img { 32 width:100%; 33 height:100%; 34 object-fit:cover 35 } 36 .av-placeholder svg { 37 stroke:#555 38 } 39 .file-input { 40 position:absolute; 41 width:1px; 42 height:1px; 43 padding:0; 44 margin:-1px; 45 overflow:hidden; 46 clip:rect(0, 47 0, 48 0, 49 0); 50 border:0 51 } 52 .av-btn { 53 padding:8px 16px; 54 border-radius:6px; 55 font-size:11px; 56 font-weight:600; 57 background:transparent; 58 color:#00FF41; 59 border:1px solid #00FF41; 60 cursor:pointer; 61 transition:all .2s; 62 font-family:system-ui, 63 sans-serif 64 } 65 .av-btn:hover { 66 background:rgba(0, 67 255, 68 65, 69 .08) 70 } 71 .av-hint { 72 font-size:10px; 73 color:#666 74 }
untitled.js wrap JavaScript
Copy 1 const avInput=document.getElementById('av-input');const avPreview=document.getElementById('av-preview');avInput.addEventListener('change',()=>{const file=avInput.files[0];if(file&&file.type.startsWith('image/')){const reader=new FileReader();reader.onload=e=>{avPreview.innerHTML='<img src="'+e.target.result+'" alt="Avatar preview"/>';avPreview.style.borderStyle='solid';avPreview.style.borderColor='#00FF41'};reader.readAsDataURL(file)}});avPreview.addEventListener('click',()=>avInput.click())
Copy 1 <div class="min-h-screen bg-[#0A0A0A] p-6 flex items-center justify-center"> 2 <div class="flex flex-col items-center gap-3"> 3 <div id="tw-avatar-preview" class="w-20 h-20 rounded-full bg-[#1A1A2E] border-2 border-dashed border-[#333] flex items-center justify-center cursor-pointer transition hover:border-[#00FF41] overflow-hidden"> 4 <svg class="w-7 h-7 text-[#555]" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"> 5 <path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/> 6 <circle cx="12" cy="7" r="4"/> 7 </svg> 8 </div> 9 <input id="tw-avatar" type="file" accept="image/*" class="sr-only"/> 10 <label for="tw-avatar" class="px-4 py-2 rounded-md text-[11px] font-semibold border border-[#00FF41] text-[#00FF41] bg-transparent cursor-pointer transition hover:bg-[rgba(0,255,65,0.08)]"> 11 Change Photo 12 </label> 13 <span class="text-[10px] text-[#666]"> 14 JPG or PNG · Max 2MB 15 </span> 16 </div> 17 </div>
Copy 1 <div class="min-vh-100 d-flex align-items-center justify-content-center p-4"> 2 <style> 3 .bs-avatar{display:flex;flex-direction:column;align-items:center;gap:12px;font-family:system-ui,sans-serif}.bs-preview{width:80px;height:80px;border-radius:50%;background:#1A1A2E;border:2px dashed #333;display:flex;align-items:center;justify-content:center;overflow:hidden;transition:border-color .2s;cursor:pointer}.bs-preview:hover{border-color:#00FF41}.bs-preview img{width:100%;height:100%;object-fit:cover}.bs-btn{padding:8px 16px;border-radius:6px;font-size:11px;font-weight:600;background:transparent;color:#00FF41;border:1px solid #00FF41;cursor:pointer;transition:all .2s}.bs-btn:hover{background:rgba(0,255,65,.08)}.bs-hint{font-size:10px;color:#666} 4 </style> 5 <div class="bs-avatar"> 6 <div class="bs-preview" id="bs-avatar-preview"> 7 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" width="28" height="28" style="stroke:#555"> 8 <path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/> 9 <circle cx="12" cy="7" r="4"/> 10 </svg> 11 </div> 12 <input type="file" id="bs-avatar" accept="image/*" class="d-none"/> 13 <label for="bs-avatar" class="bs-btn"> 14 Change Photo 15 </label> 16 <span class="bs-hint"> 17 JPG or PNG · Max 2MB 18 </span> 19 </div> 20 </div>
preview
Image Preview Grid
A grid of image thumbnails generated from selected files using URL.createObjectURL() . Each thumbnail has an overlay with file name and a remove button. Click any thumbnail to see a larger preview.
image-grid HTML CSS JS Tailwind Bootstrap Live
Copy 1 <div class="img-grid-demo"> 2 <div class="igd-grid"> 3 <div class="igd-thumb"> 4 <div class="igd-img" style="background:linear-gradient(135deg,#1a1a2e,#2d1b69)"> 5 </div> 6 <div class="igd-overlay"> 7 <span class="igd-name"> 8 design-v1.png 9 </span> 10 <button class="igd-x" aria-label="Remove"> 11 ✕ 12 </button> 13 </div> 14 </div> 15 <div class="igd-thumb"> 16 <div class="igd-img" style="background:linear-gradient(135deg,#0f2027,#203a43)"> 17 </div> 18 <div class="igd-overlay"> 19 <span class="igd-name"> 20 hero-banner.jpg 21 </span> 22 <button class="igd-x" aria-label="Remove"> 23 ✕ 24 </button> 25 </div> 26 </div> 27 <div class="igd-thumb"> 28 <div class="igd-img" style="background:linear-gradient(135deg,#0c2340,#1e5a8a)"> 29 </div> 30 <div class="igd-overlay"> 31 <span class="igd-name"> 32 team-photo.png 33 </span> 34 <button class="igd-x" aria-label="Remove"> 35 ✕ 36 </button> 37 </div> 38 </div> 39 <div class="igd-thumb"> 40 <div class="igd-img" style="background:linear-gradient(135deg,#2d1810,#5a3a28)"> 41 </div> 42 <div class="igd-overlay"> 43 <span class="igd-name"> 44 screenshot.png 45 </span> 46 <button class="igd-x" aria-label="Remove"> 47 ✕ 48 </button> 49 </div> 50 </div> 51 </div> 52 </div>
Copy 1 .img-grid-demo { 2 max-width:320px; 3 margin:16px auto; 4 padding:0 16px; 5 font-family:system-ui, 6 sans-serif 7 } 8 .igd-grid { 9 display:grid; 10 grid-template-columns:1fr 1fr; 11 gap:8px 12 } 13 .igd-thumb { 14 position:relative; 15 border-radius:8px; 16 overflow:hidden; 17 aspect-ratio:1; 18 border:1px solid #222 19 } 20 .igd-img { 21 width:100%; 22 height:100% 23 } 24 .igd-overlay { 25 position:absolute; 26 inset:0; 27 background:linear-gradient(transparent 50%, 28 rgba(0, 29 0, 30 0, 31 .7)); 32 display:flex; 33 align-items:flex-end; 34 justify-content:space-between; 35 padding:8px; 36 opacity:0; 37 transition:opacity .2s 38 } 39 .igd-thumb:hover .igd-overlay { 40 opacity:1 41 } 42 .igd-name { 43 font-size:9px; 44 color:#E0E0E0; 45 font-weight:500; 46 white-space:nowrap; 47 overflow:hidden; 48 text-overflow:ellipsis; 49 flex:1 50 } 51 .igd-x { 52 width:20px; 53 height:20px; 54 border-radius:4px; 55 border:none; 56 background:rgba(239, 57 68, 58 68, 59 .8); 60 color:#fff; 61 font-size:9px; 62 cursor:pointer; 63 display:flex; 64 align-items:center; 65 justify-content:center; 66 flex-shrink:0; 67 transition:background .2s 68 } 69 .igd-x:hover { 70 background:#EF4444 71 }
untitled.js wrap JavaScript
Copy 1 document.querySelectorAll('.igd-x').forEach(btn=>{btn.addEventListener('click',e=>{e.stopPropagation();const thumb=btn.closest('.igd-thumb');thumb.style.opacity='0';thumb.style.transform='scale(.8)';thumb.style.transition='all .2s';setTimeout(()=>thumb.remove(),200)})})
Copy 1 <div class="min-h-screen bg-[#0A0A0A] p-6 flex items-center justify-center"> 2 <div class="grid grid-cols-2 gap-2 w-full max-w-xs"> 3 <div class="relative aspect-square rounded-lg overflow-hidden border border-[#222] group"> 4 <div class="absolute inset-0 bg-gradient-to-br from-[#1a1a2e] to-[#2d1b69]"> 5 </div> 6 <div class="absolute inset-0 bg-gradient-to-t from-black/70 via-transparent to-transparent opacity-0 group-hover:opacity-100 transition flex items-end justify-between p-2"> 7 <span class="text-[9px] font-medium text-[#E0E0E0] truncate"> 8 design-v1.png 9 </span> 10 <button aria-label="Remove" class="w-5 h-5 rounded bg-[rgba(239,68,68,0.8)] text-white text-[9px] flex items-center justify-center hover:bg-[#EF4444]"> 11 ✕ 12 </button> 13 </div> 14 </div> 15 <div class="relative aspect-square rounded-lg overflow-hidden border border-[#222] group"> 16 <div class="absolute inset-0 bg-gradient-to-br from-[#0f2027] to-[#203a43]"> 17 </div> 18 <div class="absolute inset-0 bg-gradient-to-t from-black/70 via-transparent to-transparent opacity-0 group-hover:opacity-100 transition flex items-end justify-between p-2"> 19 <span class="text-[9px] font-medium text-[#E0E0E0] truncate"> 20 hero-banner.jpg 21 </span> 22 <button aria-label="Remove" class="w-5 h-5 rounded bg-[rgba(239,68,68,0.8)] text-white text-[9px] flex items-center justify-center hover:bg-[#EF4444]"> 23 ✕ 24 </button> 25 </div> 26 </div> 27 <div class="relative aspect-square rounded-lg overflow-hidden border border-[#222] group"> 28 <div class="absolute inset-0 bg-gradient-to-br from-[#0c2340] to-[#1e5a8a]"> 29 </div> 30 <div class="absolute inset-0 bg-gradient-to-t from-black/70 via-transparent to-transparent opacity-0 group-hover:opacity-100 transition flex items-end justify-between p-2"> 31 <span class="text-[9px] font-medium text-[#E0E0E0] truncate"> 32 team-photo.png 33 </span> 34 <button aria-label="Remove" class="w-5 h-5 rounded bg-[rgba(239,68,68,0.8)] text-white text-[9px] flex items-center justify-center hover:bg-[#EF4444]"> 35 ✕ 36 </button> 37 </div> 38 </div> 39 <div class="relative aspect-square rounded-lg overflow-hidden border border-[#222] group"> 40 <div class="absolute inset-0 bg-gradient-to-br from-[#2d1810] to-[#5a3a28]"> 41 </div> 42 <div class="absolute inset-0 bg-gradient-to-t from-black/70 via-transparent to-transparent opacity-0 group-hover:opacity-100 transition flex items-end justify-between p-2"> 43 <span class="text-[9px] font-medium text-[#E0E0E0] truncate"> 44 screenshot.png 45 </span> 46 <button aria-label="Remove" class="w-5 h-5 rounded bg-[rgba(239,68,68,0.8)] text-white text-[9px] flex items-center justify-center hover:bg-[#EF4444]"> 47 ✕ 48 </button> 49 </div> 50 </div> 51 </div> 52 </div>
Copy 1 <div class="min-vh-100 d-flex align-items-center justify-content-center p-4"> 2 <style> 3 .bs-grid{display:grid;grid-template-columns:1fr 1fr;gap:8px;width:100%;max-width:320px}.bs-thumb{position:relative;border-radius:8px;overflow:hidden;aspect-ratio:1;border:1px solid #222}.bs-img{position:absolute;inset:0}.bs-overlay{position:absolute;inset:0;background:linear-gradient(transparent 50%,rgba(0,0,0,.7));display:flex;align-items:flex-end;justify-content:space-between;padding:8px;opacity:0;transition:opacity .2s}.bs-thumb:hover .bs-overlay{opacity:1}.bs-name{font-size:9px;color:#E0E0E0;font-weight:500;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;flex:1;margin-right:6px}.bs-x{width:20px;height:20px;border-radius:4px;border:none;background:rgba(239,68,68,.8);color:#fff;font-size:9px;cursor:pointer;display:flex;align-items:center;justify-content:center;flex-shrink:0}.bs-x:hover{background:#EF4444} 4 </style> 5 <div class="bs-grid"> 6 <div class="bs-thumb"> 7 <div class="bs-img" style="background:linear-gradient(135deg,#1a1a2e,#2d1b69)"> 8 </div> 9 <div class="bs-overlay"> 10 <span class="bs-name"> 11 design-v1.png 12 </span> 13 <button class="bs-x" aria-label="Remove"> 14 ✕ 15 </button> 16 </div> 17 </div> 18 <div class="bs-thumb"> 19 <div class="bs-img" style="background:linear-gradient(135deg,#0f2027,#203a43)"> 20 </div> 21 <div class="bs-overlay"> 22 <span class="bs-name"> 23 hero-banner.jpg 24 </span> 25 <button class="bs-x" aria-label="Remove"> 26 ✕ 27 </button> 28 </div> 29 </div> 30 <div class="bs-thumb"> 31 <div class="bs-img" style="background:linear-gradient(135deg,#0c2340,#1e5a8a)"> 32 </div> 33 <div class="bs-overlay"> 34 <span class="bs-name"> 35 team-photo.png 36 </span> 37 <button class="bs-x" aria-label="Remove"> 38 ✕ 39 </button> 40 </div> 41 </div> 42 <div class="bs-thumb"> 43 <div class="bs-img" style="background:linear-gradient(135deg,#2d1810,#5a3a28)"> 44 </div> 45 <div class="bs-overlay"> 46 <span class="bs-name"> 47 screenshot.png 48 </span> 49 <button class="bs-x" aria-label="Remove"> 50 ✕ 51 </button> 52 </div> 53 </div> 54 </div> 55 </div>
preview
Compact File Input
A space-saving file input styled as a button. The selected filename appears next to the button so users know a file has been chosen without opening a large drop zone.
compact-input HTML CSS JS Tailwind Bootstrap Live
Copy 1 <div class="compact-upload"> 2 <input type="file" class="file-input" id="cu-input"/> 3 <label for="cu-input" class="cu-btn"> 4 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" width="14" height="14"> 5 <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/> 6 <polyline points="17 8 12 3 7 8"/> 7 <line x1="12" y1="3" x2="12" y2="15"/> 8 </svg> 9 <span> 10 Choose file 11 </span> 12 </label> 13 <span class="cu-name" id="cu-name"> 14 No file chosen 15 </span> 16 </div>
Copy 1 .compact-upload { 2 display:flex; 3 align-items:center; 4 gap:12px; 5 justify-content:center; 6 padding:32px; 7 font-family:system-ui, 8 sans-serif 9 } 10 .file-input { 11 position:absolute; 12 width:1px; 13 height:1px; 14 padding:0; 15 margin:-1px; 16 overflow:hidden; 17 clip:rect(0, 18 0, 19 0, 20 0); 21 border:0 22 } 23 .cu-btn { 24 display:inline-flex; 25 align-items:center; 26 gap:6px; 27 padding:8px 14px; 28 border-radius:6px; 29 font-size:12px; 30 font-weight:600; 31 background:#00FF41; 32 color:#0A0A0A; 33 border:none; 34 cursor:pointer; 35 transition:all .2s 36 } 37 .cu-btn:hover { 38 background:#00E63A; 39 transform:translateY(-1px) 40 } 41 .cu-btn svg { 42 stroke:#0A0A0A 43 } 44 .cu-name { 45 font-size:12px; 46 color:#A0A0A0; 47 max-width:160px; 48 white-space:nowrap; 49 overflow:hidden; 50 text-overflow:ellipsis 51 }
untitled.js wrap JavaScript
Copy 1 const cuInput=document.getElementById('cu-input');const cuName=document.getElementById('cu-name');cuInput.addEventListener('change',()=>{cuName.textContent=cuInput.files.length?cuInput.files[0].name:'No file chosen'})
Copy 1 <div class="min-h-screen bg-[#0A0A0A] p-6 flex items-center justify-center"> 2 <div class="flex items-center gap-3"> 3 <input id="tw-compact" type="file" class="sr-only"/> 4 <label for="tw-compact" class="inline-flex items-center gap-1.5 px-4 py-2 rounded-md text-xs font-semibold bg-[#00FF41] text-[#0A0A0A] cursor-pointer transition hover:bg-[#00E63A] hover:-translate-y-0.5"> 5 <svg class="w-3.5 h-3.5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"> 6 <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/> 7 <polyline points="17 8 12 3 7 8"/> 8 <line x1="12" y1="3" x2="12" y2="15"/> 9 </svg> 10 <span> 11 Choose file 12 </span> 13 </label> 14 <span id="tw-compact-name" class="text-xs text-[#A0A0A0] max-w-[160px] truncate"> 15 No file chosen 16 </span> 17 </div> 18 </div>
Copy 1 <div class="min-vh-100 d-flex align-items-center justify-content-center p-4"> 2 <style> 3 .bs-compact{display:flex;align-items:center;gap:12px;font-family:system-ui,sans-serif}.bs-btn{display:inline-flex;align-items:center;gap:6px;padding:8px 14px;border-radius:6px;font-size:12px;font-weight:600;background:#00FF41;color:#0A0A0A;border:none;cursor:pointer;transition:all .2s}.bs-btn:hover{background:#00E63A;transform:translateY(-1px)}.bs-btn svg{stroke:#0A0A0A}.bs-name{font-size:12px;color:#A0A0A0;max-width:160px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis} 4 </style> 5 <div class="bs-compact"> 6 <input type="file" id="bs-compact" class="d-none"/> 7 <label for="bs-compact" class="bs-btn"> 8 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" width="14" height="14"> 9 <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/> 10 <polyline points="17 8 12 3 7 8"/> 11 <line x1="12" y1="3" x2="12" y2="15"/> 12 </svg> 13 <span> 14 Choose file 15 </span> 16 </label> 17 <span class="bs-name" id="bs-compact-name"> 18 No file chosen 19 </span> 20 </div> 21 </div>
preview
HTML Structure
The upload zone wraps a hidden <input type="file"> with a visible <label> for click-to-browse. Drag-and-drop events are handled on the container element. Always include aria-live="polite" on the file list.
upload-structure.html wrap HTML
Copy 1 <div class="upload-zone" role="button" tabindex="0"> 2 <input type="file" class="sr-only" id="file-upload" 3 multiple accept=".jpg,.png,.pdf" /> 4 5 <label for="file-upload" class="upload-label"> 6 <svg><!-- upload icon --></svg> 7 <span>Drop files here or click to browse</span> 8 <small>JPG, PNG, PDF · Max 10MB</small> 9 </label> 10 </div> 11 12 <!-- File list after selection --> 13 <ul class="file-list" aria-live="polite"> 14 <li class="file-item"> 15 <span class="file-icon">📄</span> 16 <span class="file-name">document.pdf</span> 17 <span class="file-size">1.2 MB</span> 18 <button aria-label="Remove document.pdf">✕</button> 19 </li> 20 </ul>
Accessibility
File uploads must work for keyboard users, screen-reader users, and users with low vision. The native <input type="file"> handles a lot of behavior automatically if you keep it in the DOM and associate it with a visible label.
Keep the real <input type="file"> accessible — hide it visually with sr-only CSS rather than display:none . Associate the input with its label using for and id so the entire styled zone is clickable. Add aria-live="polite" to the file list so selections and removals are announced. Give every remove button an explicit aria-label that includes the file name. Support keyboard interaction: the label is focusable via the input, and remove buttons are reachable with Tab. Show visible error text for invalid files; do not rely on color alone. Ensure focus indicators have enough contrast against the dark backgrounds. Best Practices
ℹ info
Always validate files client-side before uploading — check type, size, and count. Server-side validation is still required, but client-side checks provide instant feedback and reduce unnecessary network traffic.
⚠ warning
Set the accept attribute on the file input to filter the file picker, but never rely on it alone for validation — users can bypass it. Always validate on the server.
✓ best practice
Use aria-live="polite" on the file list container so screen readers announce new files and removals without interrupting the current task.
Show file size and type icons in the file list so users can quickly verify their selections before uploading. Support keyboard navigation: Enter/Space to open file picker, Delete to remove selected files, Tab to navigate the list. For drag-and-drop, highlight the drop zone on dragenter and reset on dragleave /drop . Show upload progress for files over 1MB — users need feedback for large uploads. Use a progress bar with percentage and file size. Generate image previews using URL.createObjectURL() or FileReader.readAsDataURL() before uploading.