skills/html-to-pdf/SKILL.md
This skill should be used when the user asks to "design a business card", "make a printable PDF", "render HTML to PDF", "generate a postcard", "build print collateral", "set up an HTML print pipeline", or needs help with bleed, safe areas, font embedding, or QR generation for print. Provides a Playwright-based pipeline with multiple bundled templates and theme variants for business cards (minimal, watercolor light, watercolor dark) and instructions for adding new templates.
npx skillsauth add b-open-io/prompts html-to-pdfInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
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.
tools
Get recent tweets from an X/Twitter user. Use when user asks "what has @username posted", "recent tweets from", "user's X posts", "show timeline for", "what is @user saying". Requires X_BEARER_TOKEN.
data-ai
Get X/Twitter user profile by username. Use when user asks "who is @username", "get X profile", "lookup Twitter user", "find X account", "user details", "follower count for". Requires X_BEARER_TOKEN.
data-ai
Search recent X/Twitter posts by query. Returns RAW TWEETS (last 7 days). Use when user asks "search X for", "find tweets about", "what are people saying about", "Twitter search", "raw tweets about". For AI summaries/sentiment, use x-research instead. Requires X_BEARER_TOKEN.
testing
Fetch a specific tweet by URL or ID. Use when user shares an X/Twitter URL (https://x.com/... or https://twitter.com/...), asks "get this tweet", "fetch tweet", "what does this tweet say", "read this X post". Requires X_BEARER_TOKEN.