Guide

Multi-Tab Documents

Manage multiple PDFs in a tabbed interface

KViewerTabs provides a tabbed interface for managing multiple PDF documents. Each tab runs its own KViewer instance with independent annotations, form fields, and view settings.

Basic Setup

pages/documents.vue
<template>
  <div class="h-screen">
    <KViewerTabs :items="tabs" />
  </div>
</template>

<script setup lang="ts">
const tabs = [
  { id: 'contract', label: 'Contract.pdf', source: '/contract.pdf' },
  { id: 'invoice', label: 'Invoice.pdf', source: '/invoice.pdf' },
  { id: 'report', label: 'Report.pdf', source: '/report.pdf' },
]
</script>

Tab Item Structure

Each tab item accepts these properties:

PropertyTypeRequiredDescription
idstringYesUnique tab identifier
labelstringYesDisplay name in tab bar
sourcestring | Uint8Array | objectYesPDF source
iconstringNoLucide icon name (default: i-lucide-file-text)
closablebooleanNoAllow closing this tab (default: true)
viewModeViewModeNoOverride view mode for this tab
zoomnumberNoOverride zoom for this tab
shapeDetectionbooleanNoOverride shape detection for this tab

Add Tabs Dynamically

Use the template ref to add new tabs at runtime:

<template>
  <div class="h-screen">
    <KViewerTabs ref="viewerTabs" :items="initialTabs">
      <template #tabs-trailing>
        <button @click="onAddTab">+ Add</button>
      </template>
    </KViewerTabs>
  </div>
</template>

<script setup lang="ts">
const viewerTabs = ref()

const initialTabs = [
  { id: 'doc1', label: 'Document.pdf', source: '/document.pdf' },
]

function onAddTab() {
  const input = document.createElement('input')
  input.type = 'file'
  input.accept = '.pdf'
  input.onchange = async () => {
    const file = input.files?.[0]
    if (!file) return
    const source = new Uint8Array(await file.arrayBuffer())
    viewerTabs.value?.addTab({ label: file.name, source })
  }
  input.click()
}
</script>

AddTabOptions

viewerTabs.value?.addTab(
  { label: 'New.pdf', source: pdfBytes },
  {
    index: 0,        // Insert at position (default: end)
    activate: true,  // Switch to new tab (default: true)
  },
)

Remove Tabs

viewerTabs.value?.removeTab('doc1')

Use the minTabs prop to prevent closing tabs below a threshold:

<KViewerTabs :items="tabs" :min-tabs="1" />

Access Individual Viewers

Get the underlying KViewer instance for a specific tab to call its methods:

const viewer = viewerTabs.value?.getViewer('contract')
const annotations = viewer?.getAnnotations()
const bytes = await viewer?.exportPdf({ flatten: true })

Listen to Tab Events

<KViewerTabs
  :items="tabs"
  @update:active-tab="onTabChange"
  @tab-added="onTabAdded"
  @tab-close="onTabClose"
  @tab-removed="onTabRemoved"
/>
EventPayloadDescription
update:activeTabstring (tab ID)Active tab changed
tab-addedViewerTabItemNew tab was created
tab-closestring (tab ID)Tab is about to close
tab-removedstring (tab ID)Tab was removed

Customize the Tab Bar

Use slots to add content before or after the tab list:

<KViewerTabs :items="tabs">
  <template #tabs-leading>
    <span class="px-2 font-bold">Documents</span>
  </template>

  <template #tabs-trailing>
    <button @click="onAddTab">+</button>
  </template>

  <template #empty>
    <div class="flex items-center justify-center h-full">
      <p>No documents open</p>
      <button @click="onAddTab">Open a document</button>
    </div>
  </template>
</KViewerTabs>

Annotation Persistence

When switching between tabs, annotations are automatically saved and restored. You don't need to manually call getAnnotations() or importAnnotations() -- the tab manager handles this internally.

Forward Props to All Viewers

Props like stamps, userName, signatureHandlers, textLayer, readonly, and shapeDetection are forwarded to all tab viewers:

<KViewerTabs
  :items="tabs"
  :stamps="stamps"
  user-name="Jane Doe"
  text-layer
  readonly
/>

Individual tabs can override viewMode, zoom, and shapeDetection through their ViewerTabItem definition.

Copyright © 2026