Help us improve
Share bugs, ideas, or general feedback.
From bopen-tools
Renders print-ready PDFs from HTML/CSS templates using Playwright/Chromium. Bundled business card templates (minimal, watercolor light/dark) with QR generation and print guidelines for bleed, fonts, and safe areas.
npx claudepluginhub b-open-io/claude-plugins --plugin bopen-toolsHow this skill is triggered — by the user, by Claude, or both
Slash command
/bopen-tools:html-to-pdfThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Render print-ready PDFs from HTML/CSS via Playwright (Chromium). Use this for business cards, certificates, invoices, postcards, letterhead, one-pagers — anything that ends up as a printable PDF. The pipeline is template-driven so the same renderer produces minimal black-and-white cards, editorial watercolor cards, or any other style added later. For PDF post-processing (imposition, crop marks,...
README.mdreferences/creating-a-template.mdreferences/print-rules.mdscripts/qr-artistic.tsscripts/render.tstemplates/business-cards/employees/README.mdtemplates/business-cards/employees/example.jsontemplates/business-cards/minimal/card-back.htmltemplates/business-cards/minimal/card.htmltemplates/business-cards/watercolor/assets/mountains.pngtemplates/business-cards/watercolor/assets/sky.pngtemplates/business-cards/watercolor/dark/card-back.htmltemplates/business-cards/watercolor/dark/card.htmltemplates/business-cards/watercolor/light/card-back.htmltemplates/business-cards/watercolor/light/card.htmlCreates print-ready HTML documents for PDF export using brand configs, print CSS patterns, and design best practices for proposals, reports, flyers.
Generates PDF documents using React-PDF (@react-pdf/renderer) with TypeScript and JSX for reports, invoices, forms, resumes needing flexbox layouts, SVG graphics, and custom fonts.
Generates a professional B2B case study PDF from customer details with 7 layouts, 9 style presets, and 1-4 page output.
Share bugs, ideas, or general feedback.
Render print-ready PDFs from HTML/CSS via Playwright (Chromium). Use this for business cards, certificates, invoices, postcards, letterhead, one-pagers — anything that ends up as a printable PDF. The pipeline is template-driven so the same renderer produces minimal black-and-white cards, editorial watercolor cards, or any other style added later. For PDF post-processing (imposition, crop marks, front+back merging), chain into Skill(document-skills:pdf).
Trigger when the user asks for:
html-to-pdf/
├── SKILL.md
├── README.md
├── scripts/
│ ├── render.ts # Playwright renderer with template/style/theme selection
│ └── qr-artistic.ts # Artistic QR generator (round dots, finder eyes, logo overlay)
├── templates/
│ └── business-cards/
│ ├── employees/ # Per-person JSON + photos
│ │ ├── example.json
│ │ └── README.md
│ ├── minimal/ # B/W editorial style — single theme
│ │ ├── card.html
│ │ └── card-back.html
│ └── watercolor/ # Editorial style with pixel-photo + watercolor back
│ ├── assets/ # Background images for both themes
│ │ ├── sky.png
│ │ └── mountains.png
│ ├── light/
│ │ ├── card.html
│ │ └── card-back.html
│ └── dark/
│ ├── card.html
│ └── card-back.html
└── references/
├── print-rules.md # Bleed, font embed, color-profile gotchas
└── creating-a-template.md # How to add a new template / style / theme
The renderer resolves all paths from the current working directory, so copy the skill files into a project root and run from there.
# Locate the skill — this varies by install method:
# - Plugin install: ls "$CLAUDE_PLUGIN_ROOT/skills/html-to-pdf"
# - Cloned repo: SKILL_ROOT="/path/to/prompts/skills/html-to-pdf"
# - npx skills add: ls .claude/skills/html-to-pdf
# Set SKILL_ROOT to whichever applies, then:
mkdir -p /tmp/print-job && cd /tmp/print-job
cp -R "$SKILL_ROOT/." .
# Install deps once
bun init -y
bun add playwright qrcode @types/qrcode geist @fontsource-variable/inter \
@fontsource-variable/fraunces bootstrap-icons
bunx playwright install chromium
Render a business card (always run from /tmp/print-job):
# Minimal style — no theme, no photo needed
bun scripts/render.ts --template business-cards --style minimal --employee example
# Watercolor / light — needs a portrait
bun scripts/render.ts --template business-cards --style watercolor --theme light \
--employee example --photo path/to/your-portrait.png
# Watercolor / dark — same template, same data, different theme
bun scripts/render.ts --template business-cards --style watercolor --theme dark \
--employee example --photo path/to/your-portrait.png
# Add a logo to the QR center (optional, any SVG or PNG)
bun scripts/render.ts --template business-cards --style minimal --employee example \
--logo path/to/your-logo.svg
Add a new employee by dropping employees/<slug>.json (use
employees/example.json as the schema) and passing --employee <slug>.
Output lands in out/<slug>-<style>[-<theme>]-{front,back}.pdf.
| Style | Theme | Tone | When to use |
|---|---|---|---|
minimal | (none) | Editorial black-on-white, ample whitespace, blocky fade-out accent | Default. Reads as serious, professional, formal. Print shop friendly. |
watercolor | light | Cream/parchment surface, Fraunces serif name, pixel-art portrait stamped on the card, watercolor sky back | When the recipient needs to remember the card. Editorial, warmer. |
watercolor | dark | Slate-indigo surface, cyan accents, watercolor mountain night-scene back | Brand-matched to dark-mode bopen.io. Distinctive at crypto conferences. |
A new style is one directory. A new theme is one subdirectory inside a style. See references/creating-a-template.md.
These are the gotchas that break silently if you skip them — full details in references/print-rules.md.
@page size + preferCSSPageSize: true — set the trim+bleed dimensions in BOTH the CSS @page rule and Playwright's page.pdf({ width, height, preferCSSPageSize: true }). Without preferCSSPageSize, Playwright silently falls back to US Letter.
-webkit-print-color-adjust: exact — without this, Chromium strips background colors and images from the PDF. Black-background card backs come out white.
Always embed fonts via @font-face — referencing only font-family: 'Inter', system-ui without an @font-face produces a PDF using whatever system-ui is on the build machine (SF Pro on macOS) embedded as Type 3 path glyphs. Different RIPs render Type 3 differently. Templates use __NODE_MODULES__/... placeholders so fonts always come from the installed npm package.
Wait for document.fonts.ready before page.pdf() — capturing before fonts apply produces a PDF with system fallback glyphs.
Bleed — render at trim + 2×0.125 in. The template HTMLs do this; new templates must too.
Full bleed/safe-area table, ghostscript/CMYK conversion notes, and font-embed troubleshooting live in references/print-rules.md.
So templates aren't tied to a specific filesystem layout, the renderer substitutes two tokens at render time:
| Placeholder | Resolves to | Use for |
|---|---|---|
__NODE_MODULES__/... | node_modules/... (relative to working dir) | Web fonts, icon SVGs |
__ASSETS__/... | templates/<template>/<style>/assets/... | Style-specific background images |
The template HTML can be moved to any project layout — the renderer fills in the paths.
| Placeholder | From employee JSON | Notes |
|---|---|---|
__NAME__ | name | Display name |
__TITLE__ | title | Role / subtitle |
__EMAIL__ | email | HTML-escaped |
__PHONE__ | phone | Optional |
__HANDLE__ | handle | Optional, rendered with X glyph |
__PHOTO_SRC__ | --photo <path> CLI arg | Required for templates that show a portrait |
__QR_SVG__ | Generated from qrUrl | Artistic QR with optional logo overlay |
__QR_LABEL__ | qrLabel (defaults to qrUrl) | Display text below the QR |
Every employee data field is HTML-escaped in the substitution step so a hostile name field can't inject script into the rendered page.
Known limitation: the substitution map in scripts/render.ts is currently hard-coded to the business-card field set. A new template that introduces fields like __EVENT__ or __VENUE__ requires extending the renderer. The clean refactor — looping the JSON object and substituting __<KEY-UPPERCASED>__ for each value — is deferred until a second non-card template lands. See references/creating-a-template.md.
The bundled scripts/qr-artistic.ts generator builds the QR SVG by hand from the matrix returned by qrcode.create(). It supports:
Always test the rendered QR by scanning the PDF with a phone camera before sending to print. Round-dot QRs with logo overlays can occasionally fail on cheap scanners even at level H.
document-skills:pdfAfter Playwright produces the per-page PDFs, invoke Skill(document-skills:pdf) for things HTML/CSS can't do:
A new template is one directory. A new style under an existing template is one subdirectory. A new theme is one subdirectory inside a style.
templates/
└── <template>/ # e.g. business-cards, postcards, certificates
└── <style>/ # e.g. minimal, watercolor, brutalist
├── card.html # if single-theme
├── card-back.html
└── <theme>/ # if multi-theme
├── card.html
└── card-back.html
The renderer auto-discovers from the directory structure. See references/creating-a-template.md for a step-by-step walkthrough including the file fields each template HTML needs.
This skill was extracted from the print pipeline behind bopen.io's business cards, where each teammate's QR resolves through a per-card vanity URL into the site's booking flow with attribution. The templates and gotchas reflect what actually held up at a commercial print shop.