f-ui
Components

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 File before upload completes, and close when that item is removed.
  • You need previewRender, previewPlacement, or controlled previewItem.
  • Use public File Upload alone when a custom onPreview or 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

AreaBehavior
Upload surfaceAll public File Upload props (variants, transport, gates)
Preview itemControlled previewItem or uncontrolled defaultPreviewItem
ActivationList / picture-card / avatar preview → opens Document Preview
CancellationConsumer onPreview + event.preventDefault() skips the overlay
RemoveRemoving the active item clears preview and closes the overlay
Source preferencePrefers item.file over item.url so upload completion does not reload
PlacementpreviewPlacement 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-preview
FUI_PLUS_REGISTRY_TOKEN=xxx npx shadcn@latest add @f-ui-plus/file-upload-with-preview
FUI_PLUS_REGISTRY_TOKEN=xxx yarn dlx shadcn@latest add @f-ui-plus/file-upload-with-preview
FUI_PLUS_REGISTRY_TOKEN=xxx bun x shadcn@latest add @f-ui-plus/file-upload-with-preview

registryDependencies: 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 | previewRender

Security

ThreatDefault
Public package boundaryRecipe is Plus-only; public File Upload never imports viewers
Unsafe URLsInherited from File Upload + Document Preview safe URL policy
Untrusted documentsSame viewer / overlay boundaries as Document Preview

Edge Cases & Errors

CaseBehavior
preventDefault()Overlay does not open; your handler owns the action
Active item removedPreview clears automatically
Upload finishes while previewing local fileKeeps file source; no unnecessary reload
Unsupported typeDocument Preview shows the unsupported dialog
disabledMutations blocked; preview of existing items still works unless preview={false}

API Reference

Props

Extends public FileUploadProps with:

PropTypeDefaultDescription
previewItemFileUploadItem | nullControlled preview target.
defaultPreviewItemFileUploadItem | nullnullUncontrolled initial target.
onPreviewItemChange(item | null) => voidFires when the preview target changes.
previewPlacementDocument Preview placementOverlay shell (auto / dialog / drawer).
previewRenderDocument Preview render slotCustom overlay body.
documentPreviewPropsomit open/src/name/type/kindExtra Document Preview props (e.g. engineOptions).
onPreview(item, event) => voidCalled 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.

On this page