- ds
- Components
- Drop zone
Drop zone
Drop a file, pick one, or paste. Nothing is uploaded.
Code
<label class="drop">
<svg class="i" viewBox="0 0 24 24" aria-hidden="true">…</svg>
<strong>Drop a crontab here</strong>
<span>or <span class="pick">choose a file</span></span>
<span class="hint">You can also paste it with <kbd class="kbd">Ctrl</kbd> <kbd class="kbd">V</kbd></span>
<input type="file" accept=".txt,.cron,text/plain" />
</label>The whole zone is the label of a real file input, so a click anywhere opens the picker and the input stays in the tab order. Try it: drop a file, pick one, or paste text anywhere on this page. It only reads the name and size, and nothing leaves the browser.
States
Release to read itNothing is uploaded
Drop a crontab hereThat file is 4.2 MB. The limit is 1 MB.
Code
<div class="drop over">…</div> <!-- a file is being dragged over -->
<div class="drop" aria-invalid="true">…<span class="err">That file is 4.2 MB. The limit is 1 MB.</span></div>Add .over (or data-over) on dragenter and remove it on dragleave and drop: the border and the wash turn purple. Keyboard focus on the input rings the whole zone.
Compact, and a file that has been read
orders.schema.json2.1 KB · 64 lines
Code
<label class="drop sm">
<svg class="i">…</svg>
<span><strong>Drop a schema</strong> or <span class="pick">choose a file</span></span>
<span class="hint">JSON or YAML</span>
<input type="file" />
</label>
<div class="drop-file">
<svg class="i">…</svg><b>orders.schema.json</b><small>2.1 KB · 64 lines</small>
<button class="btn xs ghost" type="button">Remove</button>
</div>.sm is one line, for a toolbar or the top of a pane. Once a file is in, replace the zone with a .drop-file row, or keep the zone and put the row under it.
Wiring
const read = (file) => file.text().then(load);
zone.addEventListener('dragover', (e) => { e.preventDefault(); zone.classList.add('over'); });
zone.addEventListener('dragleave', () => zone.classList.remove('over'));
zone.addEventListener('drop', (e) => {
e.preventDefault();
zone.classList.remove('over');
if (e.dataTransfer.files[0]) read(e.dataTransfer.files[0]);
});
zone.querySelector('input').addEventListener('change', (e) => e.target.files[0] && read(e.target.files[0]));
addEventListener('paste', (e) => {
if (e.target.closest('input, textarea')) return;
load(e.clipboardData.getData('text'));
});API
| Class | On | What it does |
|---|---|---|
.drop | label, div | Dashed zone on --bg. Put an icon, a strong line, text and a .hint inside. |
.pick | span | The "choose a file" words, styled as a link. |
.over / data-over | .drop | Something is being dragged over it. |
aria-invalid="true" | .drop | Red border. Say why in an .err line. |
.sm | .drop | One line, for toolbars and pane heads. |
.drop-file | div | A file that has been read: icon, name in <b>, size in <small>, an action. |
Accessibility
- Keep the real
input type="file"inside the zone. ds.css hides it visually without removing it, so it keeps its keyboard focus and its name from the label. - Dragging is a shortcut, not the only way: the picker and paste do the same job.
- Announce the result in a
role="status"region: "orders.schema.json, 64 lines".