Skip to main content

Build reusable document components

Organize repeated PDF sections without creating a second component system.

PDF components are ordinary local Vue components whose rendered roots are Nuxt PDF primitives. Extract a component when it owns a meaningful document section or repeats in more than one place.

Keep ownership visible

Put document-specific components next to their document family:

Application files
pdfs/
├── invoice.vue
└── components/
    └── invoice/
        ├── InvoiceAddress.vue
        ├── InvoiceLine.vue
        └── columns.ts

Move a component to pdfs/components/ only when several templates use it. This keeps invoice-specific decisions out of a generic component API.

Pass typed document data

pdfs/components/invoice/InvoiceAddress.vue
<script setup lang="ts">
defineProps<{
  label: string
  lines: string[]
}>()
</script>

<template>
  <PdfView>
    <PdfText :style="{ fontSize: 8, marginBottom: 4 }">
      {{ label }}
    </PdfText>
    <PdfText v-for="line in lines" :key="line">
      {{ line }}
    </PdfText>
  </PdfView>
</template>

Import it directly in the template. Use keyed v-for, slots, and v-if as you would in another Vue component. Do not pass DOM attributes such as class or aria-*; the PDF tree has no DOM.

Share table geometry

Put widths used by both a header and row in one TypeScript module:

pdfs/components/invoice/columns.ts
export const invoiceColumns = {
  description: '58%',
  quantity: '14%',
  price: '14%',
  total: '14%',
} as const

A table is a column of rows. Give the header and every row the same flexDirection: 'row' and column widths. The header and body then cannot drift apart.

The production recipes show address blocks, shared columns, totals, continuation headers, and table-of-contents patterns.