Import
Section titled “Import”PDF is exported from a subpath (not the main barrel) so it isn’t bundled into apps that don’t use it:
import PDF from '@delightstack/components/pdf';import type { PDFAnnotation } from '@delightstack/components';Peer Dependency
Section titled “Peer Dependency”PDF requires pdfjs-dist as a peer dependency (loaded lazily at runtime):
pnpm add pdfjs-distThe PDF worker is loaded automatically from unpkg at runtime.
Basic Usage
Section titled “Basic Usage”Currently viewing page 1.
View code
<script> import PDF from '@delightstack/components/pdf';
let page = $state(1);
</script>
<PDF src="/documents/report.pdf" bind:page height="500px" />Examples
Section titled “Examples”Controlled Zoom
Section titled “Controlled Zoom”Bind the zoom level for external control.
<script> import PDF from '@delightstack/components/pdf';
let zoom = $state(1);</script>
<button onclick={() => zoom = 1.5}>150%</button><button onclick={() => zoom = 1}>100%</button>
<PDF src="/documents/report.pdf" bind:zoom />Binary Source
Section titled “Binary Source”Load a PDF from an ArrayBuffer or Uint8Array (e.g. from a file input).
<script> import PDF from '@delightstack/components/pdf';
let pdfData = $state<ArrayBuffer | undefined>();
async function handleFile(e: Event) { const file = (e.target as HTMLInputElement).files?.[0]; if (file) pdfData = await file.arrayBuffer(); }</script>
<input type="file" accept=".pdf" onchange={handleFile} />{#if pdfData} <PDF src={pdfData} />{/if}With Annotations
Section titled “With Annotations”Enable annotation tools (highlight and note modes) in the toolbar.
<PDF src="/documents/contract.pdf" annotatable onannotation={(annotation) => { console.log(annotation.type, 'on page', annotation.page, annotation.data); }}/>Minimal Viewer
Section titled “Minimal Viewer”Hide the toolbar and download button for an embedded read-only view.
View code
<PDF src="/documents/preview.pdf" show_toolbar={false} height="400px" />Single-page mode
Section titled “Single-page mode”single_page renders only the current page (no internal scroll, no page-stack), and lets an external page navigator drive page. This is the mode the Carousel uses when displaying PDF items, but you can use it directly any time you want to wire your own page controls.
single_page renders only the current page, disables internal scroll, and fits the page to the container
— designed for embedding inside Gallery/Carousel or any external page navigator.
View code
<script> import { Button } from '@delightstack/components'; import PDF from '@delightstack/components/pdf';
let page = $state(1); let total_pages = $state(0);
</script>
<PDF src="/documents/preview.pdf" bind:page single_page show_toolbar={false} fit="page" onload={(d) => total_pages = d.total_pages} />
<Button onclick={() => page--} disabled={page <= 1}>‹ Prev page</Button><span>Page {page} / {total_pages}</span><Button onclick={() => page++} disabled={page >= total_pages}>Next page ›</Button>Skeleton Loading
Section titled “Skeleton Loading”View code
<PDF src={url_when_loaded} height="600px" />| Prop | Type | Default | Description |
|---|---|---|---|
src | string | ArrayBuffer | Uint8Array | - | PDF source: URL string, ArrayBuffer, or Uint8Array. May be assigned later — the skeleton shows until it arrives |
page | number | 1 | Current page number, 1-based ($bindable) |
zoom | number | 1 | Zoom level where 1 = 100% ($bindable) |
rotation | number | 0 | Rotation in degrees (0, 90, 180, 270) |
fit | 'width' | 'height' | 'page' | 'width' | Initial fit mode |
show_toolbar | boolean | true | Show the toolbar |
show_download | boolean | true | Show the download button in the toolbar |
searchable | boolean | true | Enable text search |
annotatable | boolean | false | Enable annotation tools |
height | string | '600px' | Container height |
single_page | boolean | false | Render only the current page, disable internal scroll, and fit the page to the container. Designed for embedding inside Gallery/Carousel or any external page navigator. |
skeleton | boolean | true | Built-in loading state: shows a skeleton while the document loads (including before src is available) and dismisses itself when ready. Set false to disable |
text_layer | boolean | true | Render the selectable/searchable text layer over each page. Disable it (the Carousel does this automatically) to skip text extraction and per-glyph layout for faster rendering when selection isn’t needed. Search still works. |
id | string | auto | Element ID |
class | string | '' | Additional CSS classes |
element | HTMLElement | undefined | Bindable reference to the root element ($bindable) |
Events
Section titled “Events”| Event | Detail | Description |
|---|---|---|
onpagechange | { page: number, total_pages: number } | Fires when the current page changes |
onload | { total_pages: number } | Fires when the PDF finishes loading |
onerror | { error: Error } | Fires when the PDF fails to load |
ondownload | none | Fires when the download button is clicked |
onannotation | PDFAnnotation | Fires when an annotation is created |
PDFAnnotation
Section titled “PDFAnnotation”interface PDFAnnotation { type: 'highlight' | 'note'; page: number; data: unknown;}Accessibility
Section titled “Accessibility”- Container has
role="document"witharia-label="PDF viewer" tabindex="0"for keyboard focus- Ctrl/Cmd + F opens text search
- Ctrl/Cmd + +/- zooms in/out, Ctrl/Cmd + 0 resets zoom
- Arrow Left / Page Up goes to the previous page
- Arrow Right / Page Down goes to the next page
- Home / End jumps to the first / last page
- Search input supports Enter for next match, Shift+Enter for previous match
- Toolbar buttons have descriptive
aria-labelattributes - Page input has
aria-label="Page number" - Text is selectable for highlighting annotations
- Focus-visible outlines on all interactive elements