0

PDF Attachment List

A list of a document's embedded files, downloadable on click.

"use client";

import { PdfAttachmentList } from "@/components/pdf-attachment-list";

About

A PDF can carry other files inside it — an embedded spreadsheet, raw data, a signed original. <PdfAttachmentList /> reads those files and gives each one a row and a download button.

Most PDFs have none, so the list shows an empty state rather than collapsing the column out of your layout.

Installation

pnpm dlx shadcn@latest add pdfcn/attachment-list

Usage

import { PdfAttachmentList } from "@/components/pdf-attachment-list";
<PdfViewer documents={[{ url: "/sample.pdf" }]} className="h-[600px] flex-row">
  <PdfAttachmentList className="w-72 shrink-0 border-r" />
  <PdfViewerContent />
</PdfViewer>

<PdfViewer /> is a flex column by default, which stacks the list above the document. Override it with flex-row to put them side by side, and give the list a width — it fills its parent's height but has no width of its own.

The attachment plugin is part of the light tier <PdfViewer /> already registers, so nothing needs to be added to plugins.

Examples

Default

"use client";

import { PdfAttachmentList } from "@/components/pdf-attachment-list";

Accessibility

The list is a <ul> of <li> rows, so assistive tech announces its length and position rather than reading a flat run of buttons. Each download button carries an aria-label naming its file — "Download quarterly-figures.csv" rather than a bare "Download" repeated down the column.

A download button disables itself while its bytes are being read, so a slow attachment can't be queued twice.

API Reference

PdfAttachmentList

A <ScrollArea /> carrying data-slot="pdf-attachment-list", containing one row per embedded file. Accepts every <ScrollArea /> prop.

Fetches the attachment list once per document through the attachment plugin's capability. It has no width of its own and fills its parent's height.

The list renders in three states, all at the same size: empty while the document loads, an empty state when the document has no attachments, and the rows themselves otherwise.

Downloading

Unlike <PdfExportButton />, which hands off to the export plugin's own hidden anchor, the attachment plugin returns raw bytes and leaves saving to the caller. The component builds the Blob, the object URL, and the anchor itself, and releases the URL afterwards.

That's also the hook if you want something other than a download — a preview pane, an upload, a hand-off to another app. Call downloadAttachment on the plugin scope and do what you like with the buffer:

const { provides } = useAttachmentCapability();
 
provides
  ?.forDocument(documentId)
  .downloadAttachment(attachment)
  .wait(
    (buffer) => {
      // ArrayBuffer — save it, parse it, or hand it somewhere else.
    },
    () => {},
  );

File icons

Each row picks an icon from the attachment's MIME type, falling back to the file extension — writers fill the MIME type in inconsistently, and a wrong icon is more noticeable than a generic one. Unrecognized types get a plain file icon.

Styling the rows

The rendered markup carries slots at each level, so rows can be restyled without forking the component:

SlotElementDescription
pdf-attachment-listdivThe scroll container.
pdf-attachment-itemsulThe list of rows.
pdf-attachment-itemliA single attachment row.

Each row shows the file's name, its description and size when the document declares them, and a download button. Name and description truncate rather than wrap, so rows stay a uniform height in a narrow column.