Field
Text entry in a recess, the label printed above it, and a hint that says how to fix an error.
Kit part · File uploads · Inputs
An in-tray for files. A machined lip stands round a ribbed floor; the floor rises a little when a file is dragged over it, and a lamp on the lip lights. What goes in lands in the tray as a sheet of paper and is listed as printed rows, a refusal says why, and the key inside the tray is a real file input.
Drop files here
Any file, up to 5 MB.
Drop files here
Drop files here
Drop files here
Drop your avatar
PNG or JPEG, up to 2 MB.
Drop files here
Drop files here
Both finishes, side by side. Hover, pressed and focus are forced with .is-hover, .is-down and .is-focus; checked and disabled are real.
Aluminium
| Part | Ready | Holding files | File over it | Key focus | Full | Disabled |
|---|---|---|---|---|---|---|
| Drop zone | Drop files Ready0 of 6 | Drop files Ready2 of 6 | Drop files Release0 of 6 | Drop files Ready0 of 6 | Drop files Full6 of 6 | Drop files Off0 of 6 |
Graphite
| Part | Ready | Holding files | File over it | Key focus | Full | Disabled |
|---|---|---|---|---|---|---|
| Drop zone | Drop files Ready0 of 6 | Drop files Ready2 of 6 | Drop files Release0 of 6 | Drop files Ready0 of 6 | Drop files Full6 of 6 | Drop files Off0 of 6 |
| Name | Kind | Values | What it does |
|---|---|---|---|
| <tui-drop-zone> | element | name · accept · max-size · max-files · multiple | The tray. name: what the files post under. accept: extensions, type/* or exact types. max-size: bytes, or "512kb", "5mb". max-files: how many at most. multiple: more than one. |
| disabled | attribute | none | Switches off the drop and the picker; the tray dims to 55% and the lip says Off. |
| .tui-drop-zone__tray · __floor | class | lip · floor | The lip is a raised plate 6px round the floor. The floor is the ribbed recess and rises under a drag. Set --tui-dz-h to change its height. |
| label.tui-drop-zone__pick > input[type=file] | element | inside the floor | The key and the real input inside it. Named by the key’s words, described by the hint. Enter or Space opens the picker. |
| .tui-drop-zone__lip | class | lamp · state · count | The front edge. The lamp lights over a drag; the state reads Ready, Release to add, Full or Off; the count reads “2 of 6”. |
| data-over | attribute | set by script | On the element while a file is dragged over it. Set it yourself to hold the state. |
| .tui-drop-zone__sheets · __sheet | class | made by script | The files lying in the tray: a sheet of paper each, printed with its kind and name, the newest on top, the pile fanned a little (five at most). A sheet settles as it lands; one under the pointer comes to the top, and its × takes the file out. They cover the floor’s printing while the tray holds files (data-holding on the floor). |
| .tui-drop-zone__row | class | made by script | A printed row: extension plate, name, size and a key that removes it. With data-refused it has a red lamp and the reason in place of the size. |
| files | property | array of File | What is in the tray now. |
| add(files) | method | array of File | Offer files the way a drop does: the same checks, the same rows, the same event. |
| tui-files | event | detail.files · added · removed · rejected | After every change. Bubbles. rejected holds { file, why, hint } for each file turned away. |
| fileSize · parseSize · acceptLabel · dropAccepts · dropTake · dropHint · dropSays · dropZoneHTML · dropSheetsHTML | function | kit.mjs | The rules, pure: sizes as words, an accept list as words, what a tray takes and refuses, what to say, and the markup for a page that renders on a server, the sheets included. |
| Enter · Space | keyboard | on the key | Opens the file picker. |
<tui-drop-zone name="attachments" accept="image/png,image/jpeg,.pdf"
max-size="5mb" max-files="6" multiple>
<div class="tui-drop-zone__tray">
<div class="tui-drop-zone__floor">
<p class="tui-drop-zone__title">Drop files in the tray</p>
<p class="tui-drop-zone__hint">Takes PNG, JPEG and PDF, up to 5 MB each. 6 files at most.</p>
<label class="tui-key tui-drop-zone__pick">
<input class="tui-drop-zone__input" type="file">Choose files
</label>
</div>
<div class="tui-drop-zone__lip">
<span class="tui-lamp" aria-hidden="true"></span>
<span class="tui-drop-zone__state">Ready</span>
<span class="tui-drop-zone__count"></span>
</div>
</div>
</tui-drop-zone>import { dropZoneHTML } from '/kit/kit.mjs'; // also defines <tui-drop-zone>
// An empty <tui-drop-zone> builds the tray above for itself.
const zone = document.querySelector('tui-drop-zone[name="attachments"]');
zone.files; // the File objects in the tray
zone.add(someFiles); // offer files as a drop would
zone.addEventListener('tui-files', (e) => {
e.detail.added; // files that just went in
e.detail.rejected; // [{ file, why: 'type' | 'size' | 'duplicate' | 'full', hint }]
});
// A form posts exactly the files in the tray, under the element's name:
new FormData(form).getAll('attachments');Needs kit.css and kit.mjs. How to install the kit.
<input type="file"> inside the key, so it sits in the tab order, is named by the key’s words and opens with Enter or Space. Nothing depends on dragging.aria-describedby, so the types and the limit are read before anyone chooses.The lip is a raised plate; the floor in it is the kit’s recess with ribs drawn across, each a dark line and a lit one, like the other grooves. Six pixels of lip round a 6px floor make the tray’s 12px corner concentric. Under a drag the floor’s depth is one number, --tui-dz-depth, eased from 1 to a quarter in 160ms: the shadows that make it a recess thin out and the floor sits up a pixel or two, which is all lifting a floor means. Drag events fire for every child under the pointer, so entering and leaving are counted. What comes in, from a drop or from the picker, goes through dropTake, which is pure and tested, and the accepted files are written back into the real input through a DataTransfer, so a form posts exactly what the tray shows. What was just accepted lands as paper: sheets in the kit’s paper tokens, each turned and shifted by its place in the pile, and a new one drops the last 18px and settles in 340ms, one after the other.
A tray for files that a keyboard and a phone can use as well as a mouse.
6 rules in this file, with the foundations they rest on and the anti-patterns that break them. Full rules ship in the brain with the Pass.
Text entry in a recess, the label printed above it, and a hint that says how to fix an error.
Progress through a task: ink filling a groove, never a coloured bar.
Rows in a rack: a mark, a title, a second line and a fact or a key at the end. Chosen rows sink into a recess with their check key latched; in a running order, a row lifts out of the rack on its skirt and slides past the others, from the keyboard or by its grip.