client-document-export
Skill0 starsChanged 3 months agoMITView on GitHub
Exports webapp views to PNG and PDF from the browser: html-to-image capture, jsPDF generation, export-specific CSS, scroll/width fixes, and email attachment handoff. Use when adding download image, print, PDF export, or snapshot features to dashboards and reports.
- Installs packages
- Sends data out
In this repository23 other files it gives its agents.
- cloudflare-turnstile-nextjsSkill
- ga4-analytics-nextjsSkill
- git-repo-privacy-auditSkill
- hardened-contact-form-nextjsSkill
- html-semantic-markupSkill
- multi-step-wizard-flowSkill
- nextjs-api-routesSkill
- nextjs-app-routerSkill
- nextjs-seo-metadataSkill
- nextjs-ui-foundationSkill
- next-themes-dark-modeSkill
- playwright-e2e-testingSkill
- react-components-patternsSkill
- react-hooks-patternsSkill
- recharts-dashboardSkill
- resend-email-nextjsSkill
- server-pdf-playwrightSkill
- shadcn-ui-setupSkill
- specialized-form-inputsSkill
- tailwind-css-v4Skill
- vitest-unit-testingSkill
- web-accessibility-wcagSkill
- zustand-persist-stateSkill
The file
We split this skill into the rules it gives an agent
3 rules. About 0 are common, 2 are distinctive, and 1 is very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈2
Distinctive · estimate
This author's own choices. Worth a look.
1
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 54
Before capture, temporarily flatten scroll containers:
- Distinctive: line 68
Add a data attribute during capture for layout overrides:
- Very specific: line 152
When client capture quality/size is insufficient, generate PDF on the server with Playwright — see server-pdf-playwright.
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Client Document Export
Libraries
npm install html-to-image jspdf| Format | Library | Best for |
| PNG | html-to-image | Pixel-perfect dashboard snapshots |
jspdf | Printable reports, email attachments |
PNG capture (html-to-image)
import { toPng } from "html-to-image";
export async function captureElement(el: HTMLElement): Promise<string> {
return toPng(el, {
pixelRatio: 2,
cacheBust: true,
width: resolveExportWidth(el),
style: { overflow: "visible" },
});
}Width strategy
const EXPORT_MIN = 1280;
const EXPORT_MAX = 1440;
function resolveExportWidth(el: HTMLElement): number {
const measured = Math.ceil(el.scrollWidth);
return Math.min(EXPORT_MAX, Math.max(EXPORT_MIN, measured));
}Prevents clipped desktop layouts and oversized mobile captures.
Scroll / overflow fixes
Before capture, temporarily flatten scroll containers:
- Query all descendants with
overflow: auto|scroll|hiddenormax-height - Set
overflow: visible,maxHeight: none, expandheighttoscrollHeight - Restore styles in
finallyblock
Also wait two animation frames before capture:
await new Promise((r) => requestAnimationFrame(() => requestAnimationFrame(r)));Export mode CSS
Add a data attribute during capture for layout overrides:
<div data-export="true" ref={ref}>...</div>[data-export="true"] .scroll-area { overflow: visible !important; max-height: none !important; }
[data-export="true"] .responsive-stack { flex-direction: column !important; }
[data-export="true"] .hide-on-export { display: none !important; }Hide interactive controls (buttons, tooltips) during export.
Download trigger
function downloadDataUrl(dataUrl: string, filename: string) {
const a = document.createElement("a");
a.href = dataUrl;
a.download = filename;
a.click();
}Filename: {app-slug}-{date}.png.
PDF generation (jsPDF)
Two approaches:
A. From PNG (simple, WYSIWYG)
const png = await captureElement(el);
const pdf = new jsPDF({ orientation: "portrait", unit: "px", format: [width, height] });
pdf.addImage(png, "PNG", 0, 0, width, height);
const blob = pdf.output("blob");B. Programmatic (smaller files, multi-page)
Build PDF from data structures — better for long reports, worse for styled dashboards.
Email PDF attachment
Client generates PDF → base64 → POST to API route → Resend attachment.
const base64 = pdf.output("datauristring").split(",")[1];
await fetch("/api/send-report", {
method: "POST",
body: JSON.stringify({ toEmail, pdfBase64: base64, filename }),
});For large PDFs, generate server-side instead.
UX checklist
- [ ] Loading spinner during capture (can take 1–3s)
- [ ] Disable export button while running
- [ ] Track analytics: export_start, export_success, export_error
- [ ] Toast on failure with retry
- [ ] Test with charts (Recharts SVG), custom fonts, dark backgroundsKnown issues
| Issue | Fix |
| Clipped content | Flatten scroll + set explicit width |
| Missing fonts | Ensure fonts loaded (document.fonts.ready) before capture |
| CORS images | Use same-origin or embedded data URLs |
| Blurry output | pixelRatio: 2 |
| Huge file size | Lower pixelRatio or compress PNG |
Print alternative
For simple cases, @media print CSS + window.print() may suffice without libraries. Use export libs when users need shareable files.
Server-side alternative
When client capture quality/size is insufficient, generate PDF on the server with Playwright — see server-pdf-playwright. Keep client jsPDF as fallback when server route returns 503.
Anti-patterns
- Screenshot via
html2canvaswhenhtml-to-imageis already in stack - Capturing
document.bodyinstead of scoped dashboard root - Forgetting to restore DOM styles after failed capture (use try/finally)
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

