# PDF Dropzone

> A PDF-only upload dropzone that opens the dropped file in the shared PDF viewer.

A shadcn/ui block from the [@extend](https://ui.extend.ai) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/extend/pdf-dropzone).

- Page, with a live preview: https://shadcn.exchange/r/extend/pdf-dropzone
- Screenshot: https://abzjhwgwpdpz7ua5.public.blob.vercel-storage.com/shots/extend/pdf-dropzone-light-ed4f66d0a3ca6e0f.png
- Registry item: https://ui.extend.ai/r/pdf-dropzone.json
- Categories: documents, blocks
- Listed: 2026-10-13, source last changed 2026-10-13

## Install

```bash
npx shadcn@latest add https://ui.extend.ai/r/pdf-dropzone.json
```

Run it in a project that has shadcn/ui set up. It adds the files below and installs what they depend on.

## Dependencies

- Registry items: `@extend/file-upload`, `@extend/pdf-viewer`

## Files

### `registry/new-york-v4/blocks/pdf-dropzone.tsx`

```tsx
"use client"

import * as React from "react"

import { FileUpload } from "@/components/extend/file-upload"
import { PDFViewer } from "@/components/extend/pdf-viewer"
import { IconPlaceholder } from "@/components/icon-placeholder"

function FileUploadGlyph(props: InlineRegistryIconProps) {
  return (
    <IconPlaceholder
      lucide="FileUp"
      tabler="IconFileUpload"
      hugeicons="FileUploadIcon"
      phosphor="FileArrowUpIcon"
      remixicon="RiFileUploadLine"
      {...props}
    />
  )
}
const PDF_ACCEPT = "application/pdf,.pdf"
const PDF_UPLOAD_DROPZONE_SELECTOR = "[data-pdf-upload-dropzone]"
function isPdfFile(file: File) {
  return (
    file.type === "application/pdf" || file.name.toLowerCase().endsWith(".pdf")
  )
}
export function PdfDropzoneBlock() {
  const [pdfFile, setPdfFile] = React.useState<{
    name: string
    url: string
  } | null>(null)
  const objectUrlRef = React.useRef<string | null>(null)
  const loadPdf = React.useCallback((files: File[]) => {
    const nextFile = files.find(isPdfFile)
    if (!nextFile) return
    const nextUrl = URL.createObjectURL(nextFile)
    if (objectUrlRef.current) URL.revokeObjectURL(objectUrlRef.current)
    objectUrlRef.current = nextUrl
    setPdfFile({ name: nextFile.name, url: nextUrl })
  }, [])
  React.useEffect(() => {
    return () => {
      if (objectUrlRef.current) URL.revokeObjectURL(objectUrlRef.current)
    }
  }, [])
  if (!pdfFile) {
    return (
      <div
        className="grid h-full min-h-[680px] place-items-center bg-background p-4"
        onDragEnter={(event) => event.preventDefault()}
        onDragOver={(event) => event.preventDefault()}
        onDrop={(event) => {
          event.preventDefault()
          const target = event.target
          if (
            target instanceof Element &&
            target.closest(PDF_UPLOAD_DROPZONE_SELECTOR)
          ) {
            return
          }
          if (event.dataTransfer.files.length > 0) {
            loadPdf(Array.from(event.dataTransfer.files))
          }
        }}
      >
        <div data-pdf-upload-dropzone className="w-full max-w-xl">
          <FileUpload
            accept={PDF_ACCEPT}
            acceptedFileTypes={[{ label: "PDF", icon: FileUploadGlyph }]}
            browseLabel="Browse PDF"
            className="w-full"
            description="PDF files only"
            draggingLabel="Drop PDF"
            multiple={false}
            showFileList={false}
            title="Drop a PDF to preview"
            onFilesAccepted={loadPdf}
          />
        </div>
      </div>
    )
  }
  return (
    <div className="h-full min-h-[680px] bg-background">
      <PDFViewer
        key={pdfFile.url}
        src={pdfFile.url}
        defaultZoom={1}
        fileName={pdfFile.name}
        onPdfUpload={(file) => loadPdf([file])}
        toolbarActions={
          <div className="hidden max-w-40 truncate text-xs text-muted-foreground sm:block">
            {pdfFile.name}
          </div>
        }
      />
    </div>
  )
}
type InlineRegistryIconProps = Omit<
  React.ComponentProps<"svg">,
  "children" | "strokeWidth"
> & { strokeWidth?: number }
```
