Table of contents
FileInput
Picking files, in the two shapes a form actually wants: svelte <FileInput bind:files variant="line" accept=".csv" /> a control the size of an Input, for a field that sits in a column with other fields <FileInput bind:files multiple /> a drop area, for when choosing files IS the screen Both take a drop, both open the picker on click, and both are one real <input type="file"> underneath — so Enter and Space open the picker, the file dialog is the operating system's, and a name submits with the form.
It also writes the list back into that input after every change, which is the part hand-rolled uploaders miss: remove a file from a native input and the form still submits it, because files was never touched. Validation is accept, maxSize and maxFiles, plus a duplicate check — a file dropped twice is dropped once. Whatever is turned away is said out loud, listed under the control, and passed to onReject; nothing is rejected silently.
Forms · two-way: bind:files · submits in a form with name · keyboard contract
<script lang="ts">
import { FileInput } from '@l4st/driftwood';
</script>
| Light | Dark |
|---|---|
![]() |
![]() |
Props
| Prop | Type | Default | What it does |
|---|---|---|---|
files bind: |
File[] |
[] |
The chosen files. Bindable — removing from it updates the control. |
variant |
Variant |
'drop' |
drop is the dashed area; line is the height of an Input. |
accept |
string |
— | Native accept syntax: .csv, image/*, application/pdf, or a mix. |
multiple |
boolean |
false |
Allow more than one, and append rather than replace on each pick. |
maxSize |
number |
— | Largest single file, in bytes. Anything over is rejected by name. |
maxFiles |
number |
— | How many files may be held at once. |
disabled |
boolean |
false |
Blocks the picker and the drop target. |
name |
string |
— | Submits with the form, exactly what the list shows. |
label |
string |
— | The call to action, e.g. "Drop a backup here". |
hint |
string |
— | One line under it — the place for the accepted types and the size cap. |
showList |
boolean |
true |
Render the chosen files. Turn it off to drive your own FileList. |
onFiles |
(files: File[]) => void |
— | The chosen files, after validation. Fires on every accepted pick. |
onReject |
(rejections: FileRejection[]) => void |
— | Everything turned away, and why. |
onRemove |
(file: File, index: number) => void |
— | The removed file and the index it held. |
class |
string |
— | Merged with the component's own classes, last — so yours win. |
aria-label |
string |
— | Names the control when label is not the whole story. |
aria-describedby |
string |
— | Id of the element that names it — what Field passes. |
id |
string |
— |
Keyboard
| Key | Does |
|---|---|
| Tab | reach the picker — the whole zone is one stop, then one per chosen file |
| Enter / Space | open the file dialog |
| Cmd/Ctrl + V | add files from the clipboard, while the picker has focus |
Example
The markup behind the screenshot above, lifted from src/routes/shots/ — so it
compiles, and it is exactly what produced the image.
<FileInput
files={chosen}
multiple
hint="Archives and manifests, up to 500 MB each."
aria-label="Backups to restore"
/>
Notes
The two shapes
variant="line" is the height of an Input, so it sits in a column
of form fields without shouting. variant="drop" (the default) is the dashed
area, for a screen whose job is choosing files. Both accept a drop; the line
one just does not advertise it until you drag something over it.
| Line | Drop |
|---|---|
![]() |
![]() |
What the form submits
A native file input keeps whatever the picker handed it. Remove a file from a hand-rolled list and the input is untouched — so the form still uploads the file the user just took out, and nothing anywhere reports a problem.
FileInput assigns the edited set back into its own input after every add and
every removal, so the two never disagree. pnpm probe asserts exactly that:
pick two, submit, remove one, submit again, and read what FormData actually
carried.
That is also why removal is built in rather than left to onRemove — the
callback tells you a file went, it is not how it goes.
Validation
accept, maxSize, maxFiles and a duplicate check, applied in that order.
Nothing is refused silently: each rejection is listed under the control naming
the file and the rule, announced through a live region, and handed to
onReject as a FileRejection.
type FileRejection = {
file: File;
reason: 'type' | 'size' | 'count' | 'duplicate';
message: string;
};
Two files count as the same file when their name and size match. Not
lastModified: the same file arriving from a different source often carries a
freshly stamped mtime, and keying on it lets the obvious duplicate through.
Uploading is yours
The kit holds files and draws progress it is given; it does not know your endpoint. When bytes start moving, turn off the built-in list and drive FileList yourself:
<FileInput bind:files showList={false} onFiles={start} />
<FileList items={uploads} onRemove={(i) => cancel(uploads[i])} />
Forms · Field · Input · FileList · Textarea · CodeEditor · Combobox · RadioGroup · NumberInput · InlineEdit · Label · Select · MultiSelect · DatePicker · TagsInput · Checkbox · Switch · Slider
driftwood
Layout
Actions
Forms
- Field
- Input
- FileInput
- FileList
- Textarea
- CodeEditor
- Combobox
- RadioGroup
- NumberInput
- InlineEdit
- Label
- Select
- MultiSelect
- DatePicker
- TagsInput
- Checkbox
- Switch
- Slider
Feedback
Charts
Overlays
Navigation
Generated from the source by pnpm wiki · repository · v0.24.0


