File Dropzone
Overview
Drop files or click to pick them; the border lights up and grows while dragging over. Each file gets its own progress bar and a check when done. Includes a sample-files button.
Plain HTML, CSS and a little vanilla JS, with no framework dependency. Copy the prompt into Claude Code, Cursor, Trae or Codex and the model adds File Dropzone to your project in your colors.
Where to use it
Search boxes, AI chat inputs, form fields, file uploads, verification codes and anywhere else people type.
Install
With Builder, install it with the 8pt CLI or call it directly from an editor connected to 8pt MCP.
$ npm i -g @8pt-dev/cli && 8pt add file-dropzone
mcp › Use 8pt MCP to call get_component("file-dropzone")
Code
Source for File Dropzone. Motion uses only transform and opacity and respects the Reduce motion setting.
<div class="x-drop">
<label class="x-drop__zone">
<input type="file" multiple hidden>
<span class="x-drop__ic" aria-hidden="true"><svg viewBox="0 0 24 24" width="15" height="15" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"><path d="M12 19V6M6 11l6-6 6 6"/></svg></span>
<b>Drop files here, or click to choose</b>
<small>PNG, SVG, PDF, each up to 10MB</small>
</label>
<ul class="x-drop__list"></ul>
<button type="button" class="x-drop__demo">Add sample files</button>
</div>.x-drop{display:grid;gap:8px;width:min(320px,100%);color:var(--d-fg,#fff)}
.x-drop__zone{display:grid;justify-items:center;gap:4px;padding:16px 12px;border-radius:14px;border:1.5px dashed var(--d-line,rgba(255,255,255,.2));background:var(--d-card,#14141c);text-align:center;cursor:pointer;transition:transform .25s cubic-bezier(.2,.8,.2,1),border-color .2s,background-color .2s}
.x-drop__zone:hover{border-color:color-mix(in srgb,var(--d-accent,#4D74FF) 60%,transparent)}
.x-drop__zone.over{transform:scale(1.02);border-color:var(--d-accent,#4D74FF);background:color-mix(in srgb,var(--d-accent,#4D74FF) 10%,var(--d-card,#14141c))}
.x-drop__ic{display:grid;place-items:center;width:30px;height:30px;border-radius:50%;background:color-mix(in srgb,var(--d-accent,#4D74FF) 18%,transparent);color:var(--d-accent,#4D74FF);font:700 15px/1 var(--d-font,system-ui,sans-serif);transition:transform .3s}
.x-drop__zone.over .x-drop__ic{transform:translateY(-3px)}
.x-drop__zone b{font:600 13px/1.3 var(--d-font,system-ui,sans-serif)}
.x-drop__zone small{font:11px/1.3 var(--d-font,system-ui,sans-serif);color:var(--d-muted,#9b9ba8)}
.x-drop__list{display:grid;gap:6px;margin:0;padding:0;list-style:none}
.x-drop__list li{display:grid;grid-template-columns:1fr auto;gap:4px 10px;padding:8px 10px;border-radius:10px;border:1px solid var(--d-line,rgba(255,255,255,.1));background:var(--d-card,#14141c);font:12px/1.2 var(--d-font,system-ui,sans-serif);animation:x-drop-in .3s cubic-bezier(.2,.8,.2,1)}
@keyframes x-drop-in{from{opacity:0;transform:translateY(6px)}}
.x-drop__list li span{overflow:hidden;white-space:nowrap;text-overflow:ellipsis}
…const init = (root) => {
const zone = root.querySelector('.x-drop__zone'), input = zone.querySelector('input'), list = root.querySelector('.x-drop__list');
const timers = new Set();
const add = (name, size) => {
if (list.children.length >= 3) list.firstElementChild.remove();
const li = document.createElement('li'); li.innerHTML = `<span></span><em>0%</em><div class="x-drop__bar"><i></i></div>`;
li.querySelector('span').textContent = name; list.appendChild(li);
let p = 0; const bar = li.querySelector('i'), em = li.querySelector('em');
const tick = () => { p = Math.min(100, p + 6 + Math.random() * 16); bar.style.transform = `scaleX(${p / 100})`; em.textContent = Math.round(p) + '%';
if (p < 100) { const id = setTimeout(tick, 160); timers.add(id); } else { li.classList.add('ok'); em.textContent = '✓ ' + size; } };
tick();
};
…Subscribe to Builder to see the full source.See pricing