Components / Files

FileUpload

Drag-and-drop zone with validation, per-file progress, cancel and retry.

@ruckc/truss-ui Files

Import

import { FileUpload } from '@ruckc/truss-ui';

Playground

accept
maxSize
maxFiles

Recipe: parse a file in the browser

upload is just per-file work, so it does not have to touch the network. Parse the file and record the result in your own state. To reject a file you can handle, resolve with { error: 'message' }: the row is marked failed with that message and nothing is thrown. Resolving with nothing marks it done, and throwing still marks it failed.

With maxFiles={1} (or multiple={false}) the component works in replace mode: choosing or dropping another file aborts the current one and swaps it in, so there is no need to remove the old file first. Try a file with only a header row to see the handled error.

Drop files here or browse
Accepted: .csv
<script lang="ts">
  import { FileUpload } from '@ruckc/truss-ui';
  import type { UploadItem } from '@ruckc/truss-ui';

  let files = $state<UploadItem[]>([]);
  let summary = $state('');

  async function parse(file: File, onProgress: (pct: number) => void, signal: AbortSignal) {
    onProgress(20);
    const text = await file.text();
    if (signal.aborted) return;
    const rows = text.trim().split(/\r?\n/);
    // A handled failure: resolve with { error } instead of throwing
    if (rows.length < 2) return { error: 'Needs a header row and at least one data row' };
    summary = `${rows.length - 1} rows`;
  }
</script>

<FileUpload bind:files upload={parse} maxFiles={1} accept=".csv" label="Choose a CSV" />

Recipe: paste, hand over the file, no list

For a dropzone that takes one image and swaps the view, skip the uploader. onfiles receives the files that passed accept, maxSize and maxFiles, whether they were chosen, dropped or pasted; without upload the items just stay ready. paste takes 'zone' (or true) for the focused zone, or 'document' for the whole page. list={false} hides the file list (a rejected file is still reported in one line), and prompt replaces the visible copy with a string or a snippet while the zone stays a labelled button that opens the picker.

Drop an image here, or paste one
Accepted: image/*
<FileUpload
  paste="document"
  list={false}
  maxFiles={1}
  accept="image/*"
  prompt="Drop an image here, or paste one"
  label="Choose an image"
  onfiles={([file]) => open(file)}
/>

API

PropTypeDefaultDescription
files bindable UploadItem[][]The files added so far, with status and progress. Bind it with `bind:files`.
accept string—e.g. ".csv,image/*,application/pdf"
multiple booleantrueAllow more than one.
maxSize number—Bytes.
maxFiles number—Maximum number of files accepted.
upload (file: File, onProgress: (pct: number) => void, signal: AbortSignal) => Promise<UploadResult>—Your uploader (or any per-file work, e.g. parsing in the browser). Report progress 0–100. Resolve to mark the file done; resolve with `{ error: 'message' }` to mark it failed without throwing; throw to mark it failed too. The signal aborts when the file is removed or replaced. Omit to just collect files. With `multiple={false}` or `maxFiles={1}`, choosing another file replaces the current one.
label string'Upload files'Visible label shown above the control.
hint string—Helper text shown below the control.
disabled booleanfalseDisables the control: it cannot be focused, changed or activated.
paste boolean | 'zone' | 'document'falseAccept files from the clipboard (e.g. a pasted screenshot). `true` / `'zone'`: when the drop zone has focus. `'document'`: anywhere on the page (the listener is removed on unmount). Pasted files go through the same `accept`, `maxSize`, `maxFiles` and replace-mode rules as chosen or dropped ones. Pasted text is left alone.
onfiles event(files: File[]) => void—Called with the files that passed validation, as soon as they are added (choose, drop or paste), with or without `upload`. Without `upload`, the items simply stay `ready`: they never enter an uploading or done state.
list booleantrueShow the list of files below the zone. Set false when you consume the file yourself; a rejected file is then reported in one line under the zone.
prompt string | Snippet—Replaces the visible "Drop files here or browse" text. The whole zone stays a button that opens the file picker, named by `label`.
/docs/file-upload Truss UI · MIT