File Upload With Preview
Composes public File Upload preview events with Document Preview overlays.
Plus Registry
This component ships from registry.plus.json, not the public registry.json. Set up @f-ui-plus on the Installation page, then install @f-ui-plus/file-upload-with-preview. Public File Upload stays installable without Plus.
File Upload With Preview is a thin Plus recipe: public File Upload preview events plus controlled/uncontrolled previewItem state wired to Document Preview. Local PDF/DOCX fixtures in demos are pinned from Extend UI commit bca60b9e204ef0db248466dfa44367ad4c23d404.
When To Use
- You want click-to-preview for image / PDF / DOCX from an upload list without composing the bridge yourself.
- Preview should open from a local
Filebefore upload completes, and close when that item is removed. - You need
previewRender,previewPlacement, or controlledpreviewItem. - Use public File Upload alone when a custom
onPreviewor new-tab fallback is enough — no viewer engine required. - Apps may compose the same bridge manually; this recipe is convenience, not a second upload state machine.
Features
| Area | Behavior |
|---|---|
| Upload surface | All public File Upload props (variants, transport, gates) |
| Preview item | Controlled previewItem or uncontrolled defaultPreviewItem |
| Activation | List / picture-card / avatar preview → opens Document Preview |
| Cancellation | Consumer onPreview + event.preventDefault() skips the overlay |
| Remove | Removing the active item clears preview and closes the overlay |
| Source preference | Prefers item.file over item.url so upload completion does not reload |
| Placement | previewPlacement or documentPreviewProps.placement |
Installing
Configure @f-ui-plus and FUI_PLUS_REGISTRY_TOKEN as in Installation — Plus Registry.
FUI_PLUS_REGISTRY_TOKEN=xxx pnpm dlx shadcn@latest add @f-ui-plus/file-upload-with-previewFUI_PLUS_REGISTRY_TOKEN=xxx npx shadcn@latest add @f-ui-plus/file-upload-with-previewFUI_PLUS_REGISTRY_TOKEN=xxx yarn dlx shadcn@latest add @f-ui-plus/file-upload-with-previewFUI_PLUS_REGISTRY_TOKEN=xxx bun x shadcn@latest add @f-ui-plus/file-upload-with-previewregistryDependencies: public file-upload, Plus document-preview (and its viewer chain). For PDF overlays, configure self-hosted WASM on documentPreviewProps.engineOptions the same way as PDF Viewer; strict CSP needs worker-src blob:.
Usage
import { FileUploadWithPreview } from "@/components/f-ui/file-upload-with-preview/file-upload-with-preview";
<FileUploadWithPreview
label="Attachments"
multiple
upload={upload}
documentPreviewProps={{
engineOptions: { wasmUrl: "/vendor/pdfium.wasm" },
}}
/>Examples
Mixed Files
Pick images, PDFs, or DOCX. Preview opens from the list even while the simulated upload is still running.
Pick images, PDFs, or DOCX. Preview opens before upload finishes.
"use client";
import { FileUploadWithPreview } from "@/components/f-ui/file-upload-with-preview/file-upload-with-preview";
import { createFakeUpload } from "@/demos/file-upload/fake-upload";
export function FileUploadWithPreviewDemo() {
return (
<div className="max-w-md">
<FileUploadWithPreview
accept="image/*,.pdf,.docx,application/pdf,application/vnd.openxmlformats-officedocument.wordprocessingml.document"
description="Pick images, PDFs, or DOCX. Preview opens before upload finishes."
documentPreviewProps={{
engineOptions: { wasmUrl: "/vendor/pdfium.wasm" },
}}
label="Attachments"
multiple
upload={createFakeUpload({ durationMs: 2000 })}
/>
</div>
);
}Controlled Preview Item
Drive previewItem / onPreviewItemChange yourself — open from an external button or clear the overlay programmatically.
Controlled preview item: —
"use client";
import { useState } from "react";
import type { FileUploadItem } from "@/components/f-ui/file-upload/file-upload-types";
import { FileUploadWithPreview } from "@/components/f-ui/file-upload-with-preview/file-upload-with-preview";
import { Button } from "@/components/ui/button";
const SEED: FileUploadItem[] = [
{
uid: "seed-pdf",
name: "loan-application.pdf",
status: "done",
type: "application/pdf",
url: "/demo-documents/loan-application.pdf",
},
{
uid: "seed-docx",
name: "demo.docx",
status: "done",
type: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
url: "/demo-documents/demo.docx",
},
{
uid: "seed-image",
name: "demo.png",
status: "done",
type: "image/png",
url: "/demo-documents/demo.png",
},
];
/**
* PDF / DOCX fixtures pinned from Extend UI
* `bca60b9e204ef0db248466dfa44367ad4c23d404`.
*/
export function FileUploadWithPreviewControlledDemo() {
const [value, setValue] = useState<FileUploadItem[]>(SEED);
const [previewItem, setPreviewItem] = useState<FileUploadItem | null>(null);
return (
<div className="max-w-md space-y-3">
<div className="flex flex-wrap gap-2">
<Button
onClick={() => setPreviewItem(SEED[0] ?? null)}
type="button"
variant="outline"
>
Preview PDF
</Button>
<Button
onClick={() => setPreviewItem(null)}
type="button"
variant="outline"
>
Clear preview
</Button>
</div>
<p className="text-muted-foreground text-sm">
Controlled preview item: {previewItem?.name ?? "—"}
</p>
<FileUploadWithPreview
documentPreviewProps={{
engineOptions: { wasmUrl: "/vendor/pdfium.wasm" },
}}
label="Controlled list"
multiple
onPreviewItemChange={setPreviewItem}
onValueChange={setValue}
previewItem={previewItem}
value={value}
/>
</div>
);
}Cancellation
Call event.preventDefault() in onPreview to keep the recipe from opening Document Preview. Public File Upload still treats the handler as a full takeover.
Click a file name. The handler calls event.preventDefault() so the recipe overlay does not open.
Last action: —
"use client";
import { useState } from "react";
import type {
FileUploadItem,
FileUploadPreviewEvent,
} from "@/components/f-ui/file-upload/file-upload-types";
import { FileUploadWithPreview } from "@/components/f-ui/file-upload-with-preview/file-upload-with-preview";
const SEED: FileUploadItem[] = [
{
uid: "cancel-pdf",
name: "loan-application.pdf",
status: "done",
type: "application/pdf",
url: "/demo-documents/loan-application.pdf",
},
{
uid: "cancel-docx",
name: "demo.docx",
status: "done",
type: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
url: "/demo-documents/demo.docx",
},
];
/**
* PDF / DOCX fixtures pinned from Extend UI
* `bca60b9e204ef0db248466dfa44367ad4c23d404`.
*/
export function FileUploadWithPreviewCancellationDemo() {
const [lastAction, setLastAction] = useState("—");
return (
<div className="max-w-md space-y-3">
<p className="text-muted-foreground text-sm">
Click a file name. The handler calls{" "}
<code>event.preventDefault()</code> so the recipe overlay does not open.
</p>
<p className="text-sm">Last action: {lastAction}</p>
<FileUploadWithPreview
defaultValue={SEED}
label="Cancelled preview"
onPreview={(item: FileUploadItem, event: FileUploadPreviewEvent) => {
event.preventDefault();
setLastAction(`Cancelled overlay for ${item.name}`);
}}
/>
</div>
);
}Composition
FileUploadWithPreview
├── FileUpload (public)
│ └── onPreview(item, event)
└── DocumentPreview (when previewItem has file/url)
└── image | PdfViewer | DocxViewer | previewRenderSecurity
| Threat | Default |
|---|---|
| Public package boundary | Recipe is Plus-only; public File Upload never imports viewers |
| Unsafe URLs | Inherited from File Upload + Document Preview safe URL policy |
| Untrusted documents | Same viewer / overlay boundaries as Document Preview |
Edge Cases & Errors
| Case | Behavior |
|---|---|
preventDefault() | Overlay does not open; your handler owns the action |
| Active item removed | Preview clears automatically |
| Upload finishes while previewing local file | Keeps file source; no unnecessary reload |
| Unsupported type | Document Preview shows the unsupported dialog |
disabled | Mutations blocked; preview of existing items still works unless preview={false} |
API Reference
Props
Extends public FileUploadProps with:
| Prop | Type | Default | Description |
|---|---|---|---|
previewItem | FileUploadItem | null | — | Controlled preview target. |
defaultPreviewItem | FileUploadItem | null | null | Uncontrolled initial target. |
onPreviewItemChange | (item | null) => void | — | Fires when the preview target changes. |
previewPlacement | Document Preview placement | — | Overlay shell (auto / dialog / drawer). |
previewRender | Document Preview render slot | — | Custom overlay body. |
documentPreviewProps | omit open/src/name/type/kind | — | Extra Document Preview props (e.g. engineOptions). |
onPreview | (item, event) => void | — | Called first; preventDefault() cancels the recipe overlay. |
All other File Upload props (value, upload, variant, preview, …) pass through unchanged.
Slots
Slot styling comes from the composed pieces: File Upload classNames and documentPreviewProps.classNames. The recipe adds no extra slot keys.