File upload
A drag-and-drop file input with preview, validation, and graceful fallback to the native file picker. Wraps a hidden <input type="file"> so standard form submission (including Rails form_with) works unchanged.
Basic upload
Preview
<div class="nano-upload"
data-controller="nanoui-upload"
data-nanoui-upload-accept-value="image/*"
data-nanoui-upload-max-size-value="2097152"
data-action="dragover->nanoui-upload#onDragover dragleave->nanoui-upload#onDragleave drop->nanoui-upload#onDrop">
<input type="file" name="logo" class="nano-upload__input"
data-nanoui-upload-target="input"
data-action="change->nanoui-upload#onChange">
<label class="nano-upload__dropzone"
data-nanoui-upload-target="dropzone"
data-action="click->nanoui-upload#openPicker"
tabindex="0">
<span class="nano-upload__icon"><svg>...</svg></span>
<span class="nano-upload__prompt"><strong>Click to upload</strong> or drag and drop</span>
<span class="nano-upload__hint">PNG or JPG up to 2 MB</span>
</label>
<div class="nano-upload__preview" data-nanoui-upload-target="preview">
<img class="nano-upload__thumbnail" alt="" data-nanoui-upload-target="thumbnail">
<div class="nano-upload__meta">
<span class="nano-upload__name" data-nanoui-upload-target="name"></span>
<span class="nano-upload__size" data-nanoui-upload-target="size"></span>
</div>
<button type="button" class="nano-upload__remove"
data-action="click->nanoui-upload#remove">
<svg>...</svg>
</button>
</div>
<p class="nano-upload__error" data-nanoui-upload-target="error" hidden></p>
</div>
Values
accept(String) — MIME types / extensions (e.g.image/*,.pdf,.docx)maxSize(Number, bytes) — reject files larger than this
Targets
input— the hidden<input type="file">that carries the file into the formdropzone— label/area shown when no file is selectedpreview— container shown after a valid file is chosenthumbnail—<img>populated fromFileReaderfor image filesname/size— metadata text nodeserror— validation message container
Events
nanoui-upload:selected— fired with{ file }in detail when a valid file is chosennanoui-upload:removed— fired when the user clears the selection
Notes
- The wrapper gets
data-state="empty" | "dragover" | "filled"which CSS uses to show/hide the dropzone vs. preview. You can target this state yourself for custom animations. - Rails tip: pair with Active Storage by naming the input for your attribute (e.g.
name="merchant[logo]"); no controller changes required.