File inputs

Single file → File | null. Multiple → readonly File[]. Live handles, ready for FormData or your upload pipeline.

Category
Directive binding
Element
<input type="file"> · <input type="file" multiple>
Modifiers
none
Leaf types
File | null (single) · readonly File[] (multiple)

Pick a file in the single input to watch the avatar slot fill in with the file name + size. Pick more in the multi-input below to see them stack; the directive writes a File[] in the order the picker returned them. Both surfaces hand you live File handles you can append to FormData or stream into an upload.

File Input Demo Open in playground

form.values.avatar

null

form.values.attachments

[]

Single file → File | null

<input v-register="form.register('avatar')" type="file" accept="image/*" />
const form = useForm({
  schema: z.object({
    avatar: z.file().nullable(),
  }),
  defaultValues: { avatar: null },
})

form.values.avatar // File | null

Selecting a file writes the File handle into storage. Clearing the input (or selecting then cancelling) writes null.

Multiple files → File

<input v-register="form.register('attachments')" type="file" multiple />
const form = useForm({
  schema: z.object({
    attachments: z.array(z.file()),
  }),
  defaultValues: { attachments: [] },
})

form.values.attachments // readonly File[]

Every file in the picker selection lands in the array, in picker order. Re-selecting replaces the array; the input never accumulates across picks.

Uploading via FormData

Live File handles work directly with the upload pipelines you'd reach for anyway:

const onSubmit = form.handleSubmit(async (values) => {
  const body = new FormData()
  body.append('avatar', values.avatar!)
  for (const file of values.attachments) {
    body.append('attachments', file)
  }
  await fetch('/api/upload', { method: 'POST', body })
})

The schema's z.file() leaf type carries the constraint through validation; refinements like .min(size), .max(size), .mime([...]) surface in form.errors.<path> like any other validator.

Reset behavior

form.reset() clears the file input back to null / []. The directive also resets the underlying <input>'s value attribute, so the picker shows "No file chosen" after the reset, no stale filename hanging around.

Where to next