Skip to contentAsheeUIAsheeUI

FileUpload

A file field that lists what was chosen and never uploads it.

FileUpload is a file field: a drop zone, the platform's own picker, and a list of what was chosen. It receives and returns File objects and does nothing else with them, it never reads a file, uploads it or stores it, so where the bytes go is entirely your decision. The one rule the field applies itself is the size limit. Like every member of the field family it draws a label, a description and a message, and reports its own status.

Usage

import { FileUpload } from "@asheeui/web";

const [files, setFiles] = useState<File[]>([]);

<FileUpload
  label="Attachments"
  files={files}
  onFilesSelected={(chosen) => setFiles((current) => [...current...chosen])}
  onRemove={(file) => setFiles((current) => current.filter((next) => next !== file))}
/>

onFilesSelected reports what the reader chose and files tells the field what to list. That is the whole contract: no value to keep in sync, no request, and no list the field maintains itself. A File is the browser's own object, so it can go straight into FormData, and it is also why the field cannot list a file it was not handed.

The size limit

maxSize is a byte count. A file larger than it is left out before onFilesSelected runs, so the consumer never has to filter what it was given. The field reports the refusal itself, in its message, with the count and the limit in the reader's terms.

Drops and picks take the same path, so dragging a file past the limit is refused exactly as choosing one is. There is one message channel and the component does not stack two: a message you pass outranks the rejection, because a field never shows two things at once. accept and multiple are the picker's settings rather than validation, accept narrows what the picker offers, and a file dragged in is not re-checked against it.

The list belongs to you

The field lists the array you pass and removes the file you tell it to. Without onRemove there is no remove control on the chips, which is the right shape for a field whose files are managed by something else.

The field keeps no copy of the list: the array, the order, the duplicates and the decision to clear it all belong to the page. That is what lets a form library own the same list, and what keeps a second upload widget from writing over the first.

Props

Three values are registered and can be restated once for the whole application under components.fileupload: buttonLabel (Choose files), hint (Drag files here) and size (md). status is default, isDisabled and required are off, and maxSize is unset, which means no limit rather than a default one. The zone is drawn from the file field's own classes rather than from the field axes, so it has no radius or variant to configure.

required states the requirement twice from one prop: the marker beside the label, and the required attribute on the underlying <input type="file">, so a native form refuses an empty submission and the two cannot disagree.

Accessibility

  • The picker is a real <input type="file">: it is visually hidden but still in the tab order, so the keyboard path is the platform's own and nothing about choosing a file is drag-only. isDisabled disables that input and ignores drops, and dims the zone so the state is visible.
  • The zone is a <label> for that input, so a pointer anywhere in the zone opens the picker, and the zone shows keyboard focus through focus-within, the hidden input's focus is drawn on the visible control.
  • Each listed file is a chip whose close control is labelled Remove <file name>, so a screen reader announces which file a press removes instead of announcing a row of identical buttons.
  • A refusal is reported in the field's message with an error status, which is a live region: the reader is told which files were left out and why, in the same place a validation message appears.