Inputs
File Upload
A dropzone that takes files from all three places people have them: dropped, browsed, and — the one usually missing — pasted.
import { FileUpload } from 'omaris' Examples
Basic
One bindable prop. Drop, browse or paste — a screenshot on the clipboard is ⌘V away — and dropped folders are walked for you.
Drag & drop files here
paste with ⌘V
<script lang="ts"> import { FileUpload, type UploadFile } from 'omaris'; let files = $state<UploadFile[]>([]);</script><FileUpload bind:files class="max-w-xl" /> Rules
The limits are props, and the hint under the headline writes itself from whichever ones you set. A file that breaks one is named rather than dropped in silence.
Drag & drop files here
image/*, .pdf · up to 2.0 MB · 5 files max · paste with ⌘V
<script lang="ts"> import { FileUpload, type UploadFile } from 'omaris'; let files = $state<UploadFile[]>([]);</script><FileUpload bind:files accept="image/*,.pdf" max={5} maxSize={2 * 1000 * 1000} class="max-w-xl" /> Gallery
preview="grid" turns the list into tiles, with the remove button riding on the thumbnail. Images and video get a real preview; anything else shows its extension.
Drop images here
image/* · paste with ⌘V
<script lang="ts"> import { FileUpload, type UploadFile } from 'omaris'; let files = $state<UploadFile[]>([]);</script><FileUpload bind:files preview="grid" accept="image/*" label="Drop images here" class="max-w-xl" /> Uploading
Give it an upload function and it becomes an uploader: progress per file, cancel while in flight, retry after a failure, and concurrency in front of the queue. Resolve with whatever the server said — it lands on item.result.
Drag & drop files here
paste with ⌘V
<script lang="ts"> import { FileUpload, type UploadFile } from 'omaris'; let files = $state<UploadFile[]>([]); // Stands in for a real request so the demo has something to show. function send( item: UploadFile, { progress, signal }: { progress: (n: number) => void; signal: AbortSignal } ) { return new Promise<{ url: string }>((resolve, reject) => { let at = 0; const tick = setInterval(() => { at = Math.min(100, at + 6 + Math.random() * 10); progress(at); if (at === 100) { clearInterval(tick); resolve({ url: `/uploads/${item.name}` }); } }, 200); signal.addEventListener('abort', () => { clearInterval(tick); reject(new Error('aborted')); }); }); }</script><FileUpload bind:files upload={send} concurrency={2} class="max-w-xl" /> Anywhere
dropAnywhere takes a drop on any part of the page and raises a veil to say so, which is what a document view or an inbox wants — the target is the screen, not a box on it.
Drop a file anywhere on this page
paste with ⌘V
<script lang="ts"> import { FileUpload, type UploadFile } from 'omaris'; let files = $state<UploadFile[]>([]);</script><FileUpload bind:files dropAnywhere variant="outlined" size="sm" tone="info" label="Drop a file anywhere on this page" class="max-w-xl"/> Overridden
Every part is reachable: classes names the slots, class covers the root and wins last, and the copy is three plain strings. A height set here beats the padding the size variant ships.
Cover image
1600×900 or larger
<script lang="ts"> import { FileUpload, type UploadFile } from 'omaris'; let files = $state<UploadFile[]>([]);</script><FileUpload bind:files multiple={false} accept="image/*" tone="tertiary" variant="tonal" label="Cover image" hint="1600×900 or larger" browseLabel="Choose" class="max-w-sm" classes={{ zone: 'h-52 rounded-shape-2xl', title: 'text-tertiary', item: 'rounded-full' }}/> When to use it
Use it for
- Any place a person hands the app a file: attachments on a form, an import screen, a document drop, an avatar or cover image.
preview="grid"shows images as a gallery before they are saved. - Screenshots.
pasteis on by default, so ⌘V drops the clipboard image into the list. This is the main reason to use a dropzone over a bare<input type="file">. - Uploads with feedback. Pass
uploadand each file gets a progress bar, a cancel while in flight and a retry after it fails. - A whole folder. The picker takes one with
directory; a dropped folder is always walked. - A drop target that is the page rather than a box on it:
dropAnywhere.
Not for
- A stored file someone is editing → pick it here, then crop it in Image Cropper.
- Files already on the server, like a list of attachments with download links → List or Table. This component owns files on their way in.
- One short string of text → Text Field.
- Reordering or triaging files across columns → Kanban.
- A gallery to browse rather than edit → Image Viewer or Masonry.
Do
- Bind
filesand treat it as the state.accept,max,maxSizeandminSizeare rules about what may enter it; drop, browse and paste all go through them. - Set
accepteven when the server also checks. It filters the picker as well as the drop. - Give
namewhen the dropzone sits in a real form. The input underneath is the control, so it submits and honoursrequired. - Handle
onrejectif a refusal needs logging or showing elsewhere. The zone already names the first one under itself for a few seconds. - Return something from
upload: an id, a URL, the server's row. It is kept on the item asresult, which is what the form submits. - Use
bind:thisforopen(),start()whenautoUploadis off, andclear(). - Use
preview="none"and thefilesnippet when the list belongs somewhere else on the page.
Don't
- Use
dropAnywheretwice on one page, or leavepasteon for two dropzones. Two veils fight over one drop, and one ⌘V lands in both; give all but onepaste="focus". - Set
maxwithoutmultiple. A single-file dropzone replaces rather than refuses, which is what a new avatar should do. - Hold object URLs from
previewafter removing a file. The component revokes them when a file goes and on unmount; copy the blob if you need it longer. - Rely on
pastealone on a phone. Keep the browse button;browseLabelset tonullremoves it, and touch has no ⌘V. - Put a
maxSizehere and a different one on the server.
Quick reference
tone primary(default)secondarytertiarydestructivesuccesswarninginfo
variant dashed(default)outlinedtonalghost
size smmd(default)lg
preview list(default)gridnone
How accepted files are shown under the zone.
API
FileUpload
A dropzone that takes files from all three places people have them: dropped, browsed, and — the one usually missing — pasted.
The smallest useful call is one attribute:
That already drags, drops, browses, pastes screenshots, walks dropped folders, previews images, shows sizes and lets each file be removed. Everything past it is opt-in: accept and maxSize to filter, max to cap, preview="grid" for a gallery, and an upload function to make it send the files itself, with per-file progress, cancel and retry.
With no upload, the component is a picker: files is the state and the surrounding form does the sending.
import { FileUpload } from 'omaris' <FileUpload bind:files /> <FileUpload accept="image/*,.pdf" maxSize={5 * 1000 * 1000} upload={async (item, { progress, signal }) => { const body = new FormData(); body.set('file', item.file); const res = await fetch('/api/upload', { method: 'POST', body, signal }); progress(100); return await res.json(); }}/> Parts
Every element this renders is reachable from outside. Pass Tailwind for one part as classes={{ part: '…' }}; class covers the root and is merged last, so it always wins.
root- No description in the source yet.
zone- The drop target.
group/zoneso the artwork inside can react to the drag without any of it being wired up in JavaScript. spotlight- The pointer-following glow. Pure decoration, hidden from a11y.
trigger- The label that makes the whole zone one big file picker. It sits above the artwork and the copy — those are positioned too, and a later sibling paints over an earlier one, so without the
z-10the picker only opens where the zone happens to be empty. media- The stack of sheets that fans open when a drag comes over.
sheet- One sheet of that stack. The two behind take the surface rather than the tone, or the three of them read as one lumpy shape instead of as paper stacked on paper.
face- The front sheet — the one with the arrow on it.
glyph- The arrow drawn on it.
copy- No description in the source yet.
title- No description in the source yet.
description- No description in the source yet.
controls- The row holding the browse button and any
actions. alert- The rule that just got broken, under the zone.
summary- "3 files · 8.2 MB" and the clear-all button.
list- No description in the source yet.
item- One file.
thumb- The thumbnail, or the extension when there is nothing to show.
info- No description in the source yet.
name- No description in the source yet.
meta- No description in the source yet.
bar- The per-file progress bar; present only while it means something.
actions- The remove / cancel / retry cluster.
overlay- The full-window veil
dropAnywhereraises. overlayCard- The card inside that veil.
Props
files bindableDefaults to []
UploadFile[] The accepted files. Bindable, and the only state you need — every other prop is a rule about what may enter it.
accept string <input accept> grammar: image/*, .csv, application/pdf.
multiple Defaults to true
boolean max number Cap on how many files may be held at once.
maxSize number Largest file, in bytes. 5 * 1000 * 1000 is 5 MB.
minSize number Smallest file, in bytes — catches empty and truncated files.
directory Defaults to false
boolean Let the picker take a whole folder. Dropped folders always walk.
paste Defaults to true
boolean | 'focus' Take files from a paste. true listens on the window and skips pastes aimed at a text field elsewhere; 'focus' only listens while the dropzone itself has focus. false turns it off.
dropAnywhere Defaults to false
boolean Accept a drop anywhere on the page, with a full-window veil.
capture 'user' | 'environment' On a phone, open the camera instead of the file browser.
name string Submitted with the surrounding form, like any <input name>.
required Defaults to false
boolean disabled Defaults to false
boolean upload UploadFn Sends a file. Give it one and the component becomes an uploader.
autoUpload Defaults to true
boolean Start uploading the moment a file is accepted.
concurrency Defaults to 3
number How many uploads run at once.
variant Defaults to 'dashed'
FileUploadVariant dashed- The default: a dashed outline that firms up under a drag.
outlined- A hairline card, for a dropzone that sits inside a form.
tonal- Filled with the tone's container — the loudest of the three.
ghost- No container at all until a drag arrives.
tone Defaults to 'primary'
FileUploadTone primarysecondarytertiarydestructivesuccesswarninginfo
size Defaults to 'md'
FileUploadSize smmdlg
preview Defaults to 'list'
FileUploadPreview list rows, grid tiles, or none to render the files yourself.
listgridnone
label string The headline in the zone.
hint string The line under it. Defaults to a sentence built from the rules.
browseLabel Defaults to 'Browse files'
string | null Text of the browse button. null removes it.
onadd (files: UploadFile[]) => void Called with the files that were just accepted.
onreject (rejections: UploadRejection[]) => void Called with the files that were turned away, and why.
onchange (files: UploadFile[]) => void Called with the whole list after any change to it.
oncomplete (files: UploadFile[]) => void Called once nothing is left to upload.
class string classes FileUploadClasses Per-part Tailwind overrides. class still covers the root.
icon Snippet Replaces the fanning sheets.
title Snippet Replaces the headline.
description Snippet Replaces the line under it.
actions Snippet Extra controls beside the browse button.
file Snippet<[FileUploadRowContext]> Replaces the whole row or tile for one file.