---
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"
AttachmentsChoose 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
```