--- name: file-upload-usage description: > Use after component-usage-ux when an app needs @techsio/ui-kit FileUpload to select, validate, preview, and manage local File objects through the Zag.js compound API, including accepted/rejected files and native form behavior. metadata: component_version: "1.0.0" type: "core" library: "@techsio/ui-kit" library_version: "0.3.2" requires: "component-usage-ux zag-compound-components app-token-overrides ux-guidelines" sources: "libs/ui/src/molecules/file-upload.tsx libs/ui/src/tokens/components/molecules/_file-upload.css libs/ui/stories/molecules/file-upload.stories.tsx https://zagjs.com/components/react/file-upload" --- # @techsio/ui-kit FileUpload Usage FileUpload manages local browser `File` objects for selection, validation, preview, and removal; it does not upload files to a server. Network transport, progress, cancellation, retry, and server errors belong to the consuming app. ## UX/UI guidelines House rules come from the `ux-guidelines` skill (writing, formatting, states, where actions and feedback live). This section applies them to `FileUpload`. **Use it when** - Attaching files to a record: product images, documents, imports. - Drag-and-drop with a visible click-to-browse alternative. **Use something else when** | Need | Use instead | | --- | --- | | Picking from existing media | a media library dialog | | Pasting a URL | FormInput | **Do** - State accepted types and max size before selection (`PNG or JPG, up to 5 MB`). - Show each file with name, size (formatted), progress, and a remove action with an accessible label. - Show rejected files with the reason and how to fix it (`photo.heic isn't supported. Use PNG or JPG.`). - Upload progress, retry and server errors belong to the app — surface them per file, not only as a toast. **Don't** - Make drag-and-drop the only way to add files. - Clear already accepted files when one file is rejected. **Copy and states** - Dropzone text: `Drag files here or browse`; button label `Choose files`. ## Setup Render the required hidden input and compose accepted and rejected collections from the connected Zag API: ```tsx import { FileUpload } from "@techsio/ui-kit/molecules/file-upload" Attachments Choose files {(api) => ( <> {api.acceptedFiles.map((file) => ( ))} {api.rejectedFiles.map(({ file, errors }) => ( {errors.join(", ")} ))} )} ``` ## Public Compound API ```text FileUpload / FileUpload.Root FileUpload.Context FileUpload.Label FileUpload.Dropzone FileUpload.HiddenInput FileUpload.Trigger FileUpload.ItemGroup FileUpload.Item FileUpload.ItemPreview FileUpload.ItemPreviewImage FileUpload.ItemName FileUpload.ItemSizeText FileUpload.ItemDeleteTrigger FileUpload.ClearTrigger ``` `FileUpload.Context` passes the connected Zag API unchanged. `ItemGroup` provides `type="accepted" | "rejected"` to its items, and `Item` provides its `file` to the preview, name, size, and delete parts. `ItemName` and `ItemSizeText` render the current file values by default. `ItemPreview` renders a stable file icon by default and accepts custom content when a file-type icon is more useful. Use `ItemPreviewImage` only when an actual image thumbnail adds meaning; it creates its object URL through Zag and revokes it when the file changes or the part unmounts. There is no `Items`, `List`, `FileText`, `PropsProvider`, public store/provider, `size`, or `variant` API. Compose ordinary content around the public parts when the application needs a different layout. ## Zag Capabilities Supported root behavior comes from `@zag-js/file-upload`: ```text selection: acceptedFiles, defaultAcceptedFiles, maxFiles validation: accept, minFileSize, maxFileSize, validate state: disabled, readOnly, invalid, required input: name, allowDrop, preventDocumentDrop, directory, capture processing: transformFiles callbacks: onFileChange, onFileAccept, onFileReject localization/composition: locale, dir, translations, ids, getRootNode ``` The context API exposes `acceptedFiles`, `rejectedFiles`, `transforming`, `remainingFiles`, `maxFilesReached`, `openFilePicker`, `setFiles`, `deleteFile`, `clearFiles`, `clearRejectedFiles`, `setClipboardFiles`, `getFileSize`, and the connected Zag prop getters. Use these capabilities directly instead of adding a second file-state model. ## Core Patterns ### Keep HiddenInput for native form behavior `FileUpload.HiddenInput` is required for the native picker and form contract. Set `name` and `required` on the root; do not replace the part with a separate native file input or override its machine-owned attributes. Root `required` also renders the shared required indicator through `FileUpload.Label`. ### Render accepted and rejected files deliberately The root does not choose an accepted-only presentation. Read both collections through `FileUpload.Context`, use the matching `ItemGroup` type, and render the raw Zag rejection errors in application-appropriate copy. `FileUpload.ClearTrigger` clears both accepted and rejected files. Use `api.clearRejectedFiles()` when an action should clear only rejected files. ### Preserve controlled Zag state Use `defaultAcceptedFiles` for uncontrolled initialization. For controlled state, pass `acceptedFiles` and update it from `onFileChange`: ```tsx setFiles(acceptedFiles)} > Choose files ``` Do not mirror rejected files or validation results into another local state model unless the application must persist them outside the component lifecycle. ### Use browser-dependent capabilities honestly - Call `api.openFilePicker()` for a programmatic picker action. - Pass paste event `clipboardData` to `api.setClipboardFiles()`. - Use `directory` only where WebKit directory selection is supported. - Use `capture="user" | "environment"` as a browser hint, not a camera guarantee. - Use `transformFiles` for asynchronous local file transformations before acceptance; it still does not perform network transport. ## Common Mistakes ### HIGH Treating FileUpload as network transport Wrong: ```tsx ``` Correct: ```tsx setPendingFiles(acceptedFiles)}> ``` Send `pendingFiles` through the application's transport layer separately. ### HIGH Omitting the hidden input Wrong: ```tsx Choose files ``` Correct: ```tsx Choose files ``` Source: libs/ui/src/molecules/file-upload.tsx ### HIGH Inventing convenience parts or visual props Wrong: ```tsx ``` Correct: ```tsx {(api) => ( {api.acceptedFiles.map((file) => ( ))} )} ``` Source: libs/ui/src/molecules/file-upload.tsx ### HIGH Duplicating validation outside Zag Wrong: ```tsx validateFiles(event.target.files)} /> ``` Correct: ```tsx ``` Source: https://zagjs.com/components/react/file-upload ## Validation Commands ```sh rg -n "]*type=['\"]file|]*(onUpload|progress|retry|size|variant)" apps rg -n "FileUpload\.(Items|List|FileText|PropsProvider|RootProvider)" apps rg -U -P -n "]*className=.*(bg-|text-|border-|p-|px-|py-|rounded-)" apps ```