Use the components purely as UI, files stay in the browser, and your form or code decides what to do with them.
import { localAdapter } from "@uploadcn/core"Sometimes you don't want an upload pipeline: a form that posts files to a server action,
a desktop app, an offline draft, or a prototype. localAdapter "uploads" instantly
without a network, and every component keeps working: previews, validation, cropping,
compression, animations.
"use client"
import { localAdapter } from "@uploadcn/core"
import { toast } from "sonner"
import { Button } from "@/components/ui/button"
import {
Upload,
UploadDropzone,
UploadDropzoneDescription,
UploadDropzoneTitle,
UploadQueue,
} from "@/components/ui/upload"
const adapter = localAdapter()
/**
* No storage at all: files stay in the browser and the form submits them
* like a regular <input type="file">, e.g. to a server action.
*/
export default function UiOnlyFormExample() {
return (
<form
className="flex w-full max-w-md flex-col gap-3"
onSubmit={(event) => {
event.preventDefault()
const files = new FormData(event.currentTarget).getAll(
"attachments"
) as File[]
toast.success(`Form submitted with ${files.length} files`, {
description: files.map((file) => file.name).join(", "),
})
}}
>
<Upload adapter={adapter} name="attachments" submit="file">
<UploadDropzone size="sm">
<UploadDropzoneTitle>Attach files</UploadDropzoneTitle>
<UploadDropzoneDescription>
Nothing is uploaded, the form sends the files
</UploadDropzoneDescription>
</UploadDropzone>
<UploadQueue size="sm" variant="muted" />
</Upload>
<Button type="submit" className="self-end">
Submit form
</Button>
</form>
)
}
Submit files with a form
Set name and submit="file" on <Upload>. The selected files are added to the form
like a regular <input type="file">:
import { localAdapter } from "@uploadcn/core"
import { Upload, UploadDropzone, UploadQueue } from "@/components/ui/upload"
const adapter = localAdapter()
export default function NewPost() {
async function createPost(formData: FormData) {
"use server"
const files = formData.getAll("attachments") as File[]
// Save them wherever you like.
}
return (
<form action={createPost}>
<Upload adapter={adapter} name="attachments" submit="file">
<UploadDropzone />
<UploadQueue />
</Upload>
<button type="submit">Post</button>
</form>
)
}Transforms apply
If you crop or compress images before upload, the form submits the processed file, so the server receives the smaller version.
Read files in code
The result of each item carries the File and a preview URL:
<FileUpload
adapter={localAdapter()}
onSuccess={(item) => {
item.result.file // File
item.result.url // blob: URL for previews
}}
/>Keep files across reloads
With store: "indexeddb", files are also saved in the browser. Read them back later,
even after a reload, with getLocalFile:
import { getLocalFile, localAdapter } from "@uploadcn/core"
const adapter = localAdapter({ store: "indexeddb" })
// later
const file = await getLocalFile(item.id)Remove them with deleteLocalFile(id) when you're done.