# PDF Pages

A PDF part of a packet, painted page by page, or a named attachment when it cannot be painted.

Canonical URL: https://docs.asterdoc.com/components/pdf-pages/

`PdfPages` is the one component here that renders content this package never
composed: a registry form filled through its official PDF layer, or a
document a vendor uploaded. A PDF already has its own pages at its own
paper size, so `PdfPages` paints exactly those pages rather than planning new
ones, and keeps that paper size even inside a packet branded to another.

## Preview


```tsx
import { Bundle } from "@/components/asterdoc/bundle";
import { Part } from "@/components/asterdoc/part";
import { PdfPages } from "@/components/asterdoc/pdf-pages";
import { vendorPacketAnnexBytes } from "@/artifacts/asterdoc/vendor-packet.annex";

export function PdfPagesDemo() {
  return (
    <Bundle id="pdf-pages-demo" className="flex flex-col gap-8">
      <Part id="insurance-annex" kind="annex" label="Certificate of insurance · annex">
        <PdfPages bytes={vendorPacketAnnexBytes} filename="certificate-of-insurance.pdf" />
      </Part>
    </Bundle>
  );
}
```


## Installation


```bash
npx shadcn@4 add @asterdoc/pdf-pages
```


```bash
asterdoc add pdf-pages
```


Installs to components/asterdoc/pdf-pages.tsx.


## Usage


```tsx
import { PdfPages } from "@/components/asterdoc/pdf-pages";

<PdfPages bytes={vendorPacketAnnexBytes} filename="certificate-of-insurance.pdf" />
```

| Prop | Type | Optional | Default | Description |
| --- | --- | --- | --- | --- |
| `bytes` | `Uint8Array` | No |  | The PDF to paint.

**The reference must be stable.** Painting restarts whenever this changes,
and a host that builds a fresh `Uint8Array` on every render repaints
forever. Hold the bytes in state or a memo, as the sealed packet's own
`parts[n].content` already is. |
| `filename` | `string` | No |  | Name to fall back to when the content cannot be painted. |
| `mimeType` | `string` | Yes | "application/pdf" | What the content is. |
| `workerSrc` | `string` | Yes |  | Where pdf.js loads its worker from. See `paintPdfPages`. |
| `standardFontDataUrl` | `string` | Yes |  | Where pdf.js loads the standard fourteen fonts from. See `paintPdfPages`. |
| `cMapUrl` | `string` | Yes |  | Where pdf.js loads its CMaps from. See `paintPdfPages`. |
| `scale` | `number` | Yes | 96 / 72 | CSS pixels per PDF point. See `paintPdfPages`. |
| `timeoutMs` | `number` | Yes | 20000 | How long the paint may take before the part becomes an attachment. |
| `onPaint` | `(report: PdfPaintReport) => void` | Yes |  | Called once the paint has settled, painted or not. |
| `className` | `string` | Yes |  | Application-owned classes on the frame the painted pages sit in. |


## Composition

`PdfPages` usually sits inside a [`Part`](/components/part) of kind `"form"`
or `"annex"`, alongside the header that names the part and, once the packet
is sealed, the pages it occupies. Nothing requires that — the "Standalone"
variant below renders `PdfPages` with no `Part` around it.

`bytes` must be a stable reference: painting restarts whenever it changes, so
a caller that builds a fresh `Uint8Array` on every render repaints forever.
Hold the bytes in state or a memo, the way a sealed packet's own part content
already is.

Painting is the specification's happy path, not its only path. When the
content cannot be opened — no PDF viewer installed, or bytes that are not a
readable PDF — `PdfPages` renders `Attachment` instead: a visible card naming
the file and why it could not be painted, so the part stays in the packet
rather than leaving a gap. The "Attachment fallback" variant below shows
exactly that.

The attachment card's and the painting note's sizes follow the document's
[typography](/components/typography) token.

## Variants

### Standalone

`PdfPages` on its own, with no `Part` around it.


```tsx
import { PdfPages } from "@/components/asterdoc/pdf-pages";
import { vendorPacketAnnexBytes } from "@/artifacts/asterdoc/vendor-packet.annex";

export function PdfPagesVariantStandalone() {
  return <PdfPages bytes={vendorPacketAnnexBytes} filename="certificate-of-insurance.pdf" />;
}
```


### Attachment fallback

Content pdf.js cannot open renders as the named `Attachment` card instead of
pages.


```tsx
import { PdfPages } from "@/components/asterdoc/pdf-pages";

/** Deliberately not a PDF, so painting fails and the attachment fallback shows. */
const UNPAINTABLE_BYTES = new TextEncoder().encode("Not a real PDF.");

export function PdfPagesVariantAttachment() {
  return <PdfPages bytes={UNPAINTABLE_BYTES} filename="unreadable-scan.pdf" />;
}
```


### Custom frame

A `className` replaces the default gray frame around the painted pages.


```tsx
import { PdfPages } from "@/components/asterdoc/pdf-pages";
import { vendorPacketAnnexBytes } from "@/artifacts/asterdoc/vendor-packet.annex";

export function PdfPagesVariantStyled() {
  return (
    <PdfPages
      bytes={vendorPacketAnnexBytes}
      filename="certificate-of-insurance.pdf"
      className="w-full overflow-hidden rounded-lg border border-neutral-300 bg-white p-4"
    />
  );
}
```
