Ray0907/pdfwind

Vue 3 + Tailwind v4 components and document blocks (invoices, reports, forms, tickets) that render to paged, selectable-text PDFs with Takumi, in Node or the browser. 11 themes + a Theme Builder. Inspired by pdfcn.

★ 0Forks 0JavaScriptGitHub ↗Compare

Project website ↗

componentsdocument-generationinvoicenuxtpdfpdf-generationtailwindcsstakumivuevue3

README

pdfwind

Vue components in, paged PDFs out — in Node or the browser, without a headless browser.

MIT Alpha 0.1.0 Vue 3

Component showcase rendered as a PDF

Why pdfwind?

  • Vue 3 + Tailwind v4: compose documents with semantic colors like bg-primary and text-muted-foreground.
  • 24 components (29 exports including the Table parts) and 20 blocks for invoices, reports, forms, tickets and more.
  • Takumi PDFs with selectable text: the same document components render in Node and the browser, with pagination and repeating headers/footers.
  • 11 runtime-switchable themes and a live Theme Builder with WCAG contrast feedback and CSS export/import.

Inspired by pdfcn, the React equivalent, and PDFx. pdfcn's MIT designs, props, theme values and chart math are ported to Vue; PDFx is inspiration only, with no copied code. See THIRD_PARTY_NOTICES.

Status and scope

Alpha 0.1.0 — not published to npm. Clone and install a local tarball; the package remains private. Vue only, not React. A live playground is hosted on Cloudflare (static build; pick any component or block, switch themes, open the Theme Builder), and you can also run it locally. The components are Vue SFCs: use a Vite-like bundler with the Vue plugin, ?raw, ?url and import.meta.glob. Ordinary Node cannot import .vue files; the Node example below uses Vite SSR.

Use with Vue / Node

Requires Node 22.12+. From a clone, pack the library, then install it in a separate project:

git clone https://github.com/Ray0907/pdfwind.git
cd pdfwind
pnpm install
npm pack --pack-destination /tmp
mkdir -p /tmp/readme-consumer && cd /tmp/readme-consumer
npm init -y
npm pkg set type=module
npm install ../pdfwind-0.1.0.tgz vue
npm install -D vite @vitejs/plugin-vue

Save this as vite.config.js (used by both examples):

import vue from "@vitejs/plugin-vue";
export default {
  plugins: [vue()],
  optimizeDeps: { exclude: ["pdfwind", "takumi-pdf"], include: ["pdfwind > qrcode"] },
  ssr: { noExternal: ["pdfwind"] },
};

Node: save as node.mjs, then run node node.mjs to write invoice.pdf:

import { createServer } from "vite";
import { readFile, writeFile } from "node:fs/promises";
import { renderPdf } from "pdfwind/node";
const vite = await createServer({ server: { middlewareMode: true, ws: false }, appType: "custom" });
try {
  const { InvoiceModern, InvoiceFooter, blockPage } = await vite.ssrLoadModule("pdfwind");
  await writeFile("invoice.pdf", await renderPdf(InvoiceModern, {}, { ...blockPage, footer: InvoiceFooter, theme: "vivid" }));
} finally { await vite.close(); }

Browser: save as App.vue:

<script setup>
import { PdfPreview, InvoiceModern, InvoiceFooter, blockPage } from "pdfwind/browser";
const options = { ...blockPage, footer: InvoiceFooter, theme: "vivid" };
</script>
<template><div style="height:90vh"><PdfPreview :component="InvoiceModern" :options="options" /></div></template>

Save main.js as import { createApp } from "vue"; import App from "./App.vue"; createApp(App).mount("#app"); and index.html as <!doctype html><html lang="en"><head><meta charset="UTF-8"><title>pdfwind</title></head><body><div id="app"></div><script type="module" src="/main.js"></script></body></html>. Run npx vite for development or npx vite build for a production bundle. For Nuxt server rendering and a client-only preview, see the Nuxt example and setup notes.

Gallery

Changing the primary color in the Theme Builder updates the PDF live

Themes applied to the same invoice Theme Builder with live PDF and contrast feedback Invoice blocks

More PDFs: reports, other blocks, small formats and dark paper

Report blocks Document blocks Ticket and shipping label Components on dark paper

Themes and Theme Builder

Choose default, blueprint, corporate, elegant, executive, forest, minimal, modern, professional, vivid or dark via the theme option, as in the examples above. Unknown names throw an error listing valid themes. Fonts load lazily; pdfcn's base-14 names map Helvetica → Inter, Times-Roman → Lora, Courier → Source Code Pro.

Run pnpm dev in the clone and open http://localhost:5173/?view=builder. Edit colors, fonts, sizes, gaps and margins with live contrast feedback, undo/redo, and CSS import/export. Contrast feedback is not a guarantee that every theme meets WCAG. Download theme.css, put it beside node.mjs, and add this inside its try block, after loading the components:

const themeCss = await readFile("theme.css", "utf8");
await writeFile("custom.pdf", await renderPdf(InvoiceModern, {}, { ...blockPage, footer: InvoiceFooter, theme: "vivid", themeCss }));

In Vite, use import themeCss from "./theme.css?raw" and pass it in options; bundled CSS is also exported, e.g. pdfwind/theme-css/vivid?raw. Re-import exported CSS into the Builder to continue editing. Exported margin variables are advisory: pass margin explicitly when rendering.

Limitations

  • Not browser CSS: individual rotate utilities are ignored (use transform); text opacity can clip glyphs; one-sided dashed/dotted borders need SVG; transparent repeating gradients can leave artifacts; fixed-position elements reserve no layout space. See technical notes and known gaps.
  • Fonts: bundled Latin fonts and Noto Sans TC cover the examples, not all writing systems. Supply covering custom fonts for Hangul, emoji or Arabic; font coverage alone does not guarantee shaping/layout support.
  • Preview: replacing a PDF resets the viewer's scroll position to page 1. Node SFC loading requires Vite SSR, as above.
  • First-render download: on the hosted playground (Cloudflare static assets, Brotli) a fresh Chromium measured ~2.3 MB over the wire for the first English render (the Takumi WASM is 4.1 MB raw, 1.7 MB compressed) and ~7.7 MB when Chinese text loads the bundled Noto Sans TC font (5.4 MB, already WOFF2). First PDF appeared after about 1.5-3.4 s in that run (one run, headless, one network; not a benchmark). These are playground measurements, not a fixed library cost; the uncompressed figures (5.1 / 10.5 MB) come from pnpm e2e:static against a plain static server.
  • Hosting/CI: CI runs the offline suite on Ubuntu for every push. Its first runs (937 pass, 3 fail, 41 skip) exposed three macOS-only assumptions in the tests (a shell md5 command and a Ctrl+Z key); the fixes are in the repository, so check the Actions tab for the current result. The hosted playground is a manual static deployment, not tied to CI.

For LLMs and agents

llms.txt — reference index. llms-full.txt — component/block props, rendering options, themes and runnable examples.

Development

pnpm install
pnpm dev
pnpm check       # tokens, offline docs links/anchors, generated llms staleness
pnpm test        # offline/headless E2Es; explicit SKIP for network-only checks
pnpm test:all    # adds public comparisons, Nuxt and packed consumer; needs network

Use pnpm 10; E2Es need Poppler, ImageMagick and the lockfile-matching Playwright Chromium. Setup, coverage and reports: CONTRIBUTING.

Credits and licence

MIT. pdfcn by shadcn-labs supplies the ported design; PDFx by akii09 inspired the project. Bundled fonts use SIL OFL; see full notices. Contribute · Code of conduct · Report a vulnerability privately.

Contributors

Ray0907

Issues