Cloud|

@k2b/ui

FileDropzone

Accessible click and drag file selection with validation state.

FileDropzone@k2b/ui

The package owns file selection and accessible interaction state; validation, upload, persistence, and transformed image ownership stay with the application.

TSX

Copy
<FileDropzone
label="Attachment"
accept="image/*"
multiple={false}
subtitle="PNG, JPG, or WebP"
hint={lastFile() ? `Selected ${lastFile()}` : "One image"}
onDrop={upload}
/>

FileDropzone provides file-picker and drag-and-drop input. The parent owns validation beyond file type, upload progress, persistence, and the list of accepted files.

Use FileDropzone

Use it for uploads where dropping files is a useful primary interaction.

Use ImageInput for one image with an immediate transformed preview.

Use FileDropTarget to let a whole page area or dialog receive files. A FileDropzone inside such an area is the more specific target: it takes the files dropped on it, and its title shows its sentence meanwhile; the area's overlay then keeps its chip hidden, so the sentence appears once. That sentence is "Drop to upload" unless dropLabel says what dropping does here, such as "Drop to convert the document to Markdown". Pass it whenever dropping does something other than upload.

Import

tsx
import {
  FileDropzone,
  type FileDropzoneProps,
} from "@k2b/ui";

Files and upload state

onDrop receives a File[] from either interaction. multiple defaults to true; when it is false, only the first file is emitted.

accept is a reactive string prop and is read when a picker or drop happens, so changing a file policy does not require remounting the field. It is passed to both the hidden file input and the drop handling. Dropped files that do not match are left out and named in one error toast. Validate size, content, and permissions again before upload.

Drops follow the shared FileDropTarget rules: only files dragged from outside the page count, an open modal dialog takes precedence over the page behind it, and the surface shows its drop state with data-file-drop="over" or data-file-drop="invalid".

The parent reports progress through busy. While busy, the dropzone is disabled and shows its loading state. error accepts visible JSX. title, subtitle, hint, and icon describe the upload task; while files are over the surface, its title shows dropLabel.

choose replaces the device's file dialog on click, for example with a chooser that offers other sources too. It is called inside the click, so it may still open the device's dialog, and resolves the chosen files, or [] when the user cancels; they reach onDrop like dropped files, with multiple applied. A rejection shows its message as the field's error until the next choice or drop. With choose, the zone renders no hidden file input, so the zone is the only file control: a dialog's first focus or a key press cannot open the device's dialog past choose.

The component does not retain selected files. Store them or start the upload in onDrop.

API reference

See shared field props for FieldProps, ValueFieldProps<T> and MaybeAccessor<T>.

ts
type FileDropzoneProps = FieldProps & {
  accept?: string; multiple?: boolean; busy?: boolean; icon?: string; title?: JSX.Element;
  subtitle?: JSX.Element; hint?: JSX.Element; dropLabel?: string; choose?: () => Promise<readonly File[]>;
  onDrop: (files: File[]) => void | Promise<void>;
};

accept uses native file-input syntax, such as "image/*,.pdf". It filters the picker and dropped files. The callback may be asynchronous; the host controls busy and error feedback. multiple=true; busy and disabled default to false.

Accessibility

The drop surface is a button, so it works with keyboard activation. Provide label, aria-label, or a clear title that names the expected file.

Do not communicate file restrictions only through an icon or invalid-drag color. State them in subtitle or hint.

Runtime

File selection and drag events require browser code. Server-rendered markup provides the initial field surface.

Example

tsx
<FileDropzone
  label="Attachment"
  accept="application/pdf"
  multiple={false}
  subtitle="PDF, up to 10 MB"
  busy={upload.loading()}
  error={upload.error()}
  onDrop={(files) => upload(files[0]!)}
/>;

Connect a coding agent

The Cloud skill provides compact working instructions. MCP supplies exact current documentation when details matter.

CLI command
bunx skills add https://docs.example.com

This command installs the working instructions published by this website in the selected coding agent.

For exact, current details, also connect the MCP server through one of the agent tabs.

Installation uses the open-source Vercel Skills CLI.

The skill and MCP complement each other: the skill describes workflows, while MCP supplies current documentation.