Ditching PDF Libraries for Modern CSS: What Actually Works
Tired of clunky PDF libraries? Discover how modern CSS, with features like @page and break-inside, lets you create print-perfect documents directly from HTML.
Alex Ivanov
A frontend developer and CSS enthusiast passionate about modern web platform capabilities.
Ditching PDF Libraries for Modern CSS: What Actually Works
For years, the developer workflow for generating documents like invoices, reports, or concert tickets has been a familiar, if slightly dreaded, routine. You get the data, you fire up a server-side library like FPDF or a client-side one like jsPDF, and you painstakingly begin to draw your document, one coordinate at a time. It feels a bit like using MS Paint to recreate the Mona Lisa—powerful, yes, but clunky and completely disconnected from the web you're building.
This process has always felt like a detour. We spend our days crafting beautiful, responsive user interfaces with HTML and CSS, only to switch to a completely different, imperative paradigm for document generation. What if we didn't have to? What if we could leverage the same tools we already know and love to create pixel-perfect, print-ready PDFs?
The good news is, you can. Thanks to advancements in CSS and the power of modern browsers, generating beautiful PDFs directly from your HTML is not just a dream; it's a practical, and often superior, alternative. In this post, we'll dive into the modern CSS features that make this possible, explore when it's the right choice, and show you what actually works in the real world.
The Old Way: Why We Reached for PDF Libraries
Traditional PDF libraries, whether they run on the server (like PrinceXML, TCPDF) or in the browser (jsPDF), have long been the go-to solution. They offer granular control over document creation. You can say, "Put this text at x=10, y=15," or "Draw a line from here to there."
This approach has its benefits:
- Precision: Absolute control over the placement of every element.
- Self-Contained: They don't rely on a browser's rendering engine.
- Advanced Features: Many support PDF-specific features like interactive forms, annotations, and encryption.
However, the downsides are significant. You're essentially maintaining two separate templates: one for the web (your HTML and CSS) and one for the PDF (your library-specific code). This violates the DRY (Don't Repeat Yourself) principle and doubles the maintenance effort. Any change to the invoice layout means updating it in two different places, with two different technologies. It's inefficient and error-prone.
The New Way: CSS for Paged Media
The modern approach flips the script. Instead of building a PDF from scratch, we style an HTML document for print, just like we'd style it for a mobile device. The core technology behind this is the CSS Paged Media Module. It provides a set of CSS rules and properties specifically for handling paged content, like a printed book or, in our case, a PDF.
The central concept is the @media print
query. You can define a whole set of styles that only apply when the page is being printed (or saved as a PDF). This allows you to hide navigation, change layouts, adjust fonts, and control page breaks—all without touching your original HTML structure.
The beauty of this is its simplicity: your HTML document is the single source of truth. The content is the content. You just provide a different stylesheet for a different output medium.
Key CSS Properties You Need to Know
To master CSS for PDF generation, you need to get familiar with a few key at-rules and properties. While browser support can vary for the most esoteric features, the essentials are widely supported in modern evergreen browsers.
The @page
At-Rule
This is your primary tool for defining the page itself. You can set the size, margins, and even orientation.
@media print {
@page {
size: A4;
margin: 1in;
}
}
The @page
rule also has pseudo-classes like :first
, :left
, and :right
for creating different layouts for the first page or for facing pages in a booklet.
Page Margin Boxes & Counters
Want to add page numbers or a document title in the header or footer? Page margin boxes are here to help. You can target these areas and insert content using CSS counters.
body {
counter-reset: page;
}
@page {
@bottom-right {
content: "Page " counter(page) " of " counter(pages);
font-size: 9pt;
color: #666;
}
}
This snippet automatically numbers your pages and places the text "Page X of Y" in the bottom-right corner of every page's margin.
Controlling Page Breaks (Fragmentation)
This is the most critical part of creating a professional-looking document. You don't want a page break right in the middle of a crucial image or a single row of a table. The fragmentation properties give you control.
break-before: avoid;
/break-after: avoid;
: Prevents a page break immediately before or after an element. Great for keeping a heading with its following paragraph.break-inside: avoid;
: The most useful one. Apply this to a figure, a table row, or a card component to prevent it from being split across two pages.orphans: 2;
/widows: 2;
: Ensures that at least two lines of a paragraph remain at the bottom (orphans) or top (widows) of a page, preventing awkward single lines.
Putting It All Together: A Practical Example
Let's imagine we have a simple HTML invoice. On the screen, it might have a blue header and some interactive elements. For the PDF, we want a clean, black-and-white A4 document with page numbers.
Here's a simplified version of our print stylesheet:
/* styles.print.css */
@media print {
body {
font-family: 'Times New Roman', serif;
font-size: 12pt;
color: #000;
}
/* Hide navigation and other web-only elements */
.main-nav, .actions-button, .web-footer {
display: none;
}
/* Define the page box */
@page {
size: A4;
margin: 2cm;
@bottom-center {
content: counter(page);
font-style: italic;
}
}
h1, h2, h3 {
/* Keep headings with the content that follows */
break-after: avoid;
}
/* Prevent table rows and images from splitting */
tr, img {
break-inside: avoid;
}
/* Ensure table headers repeat on every new page */
thead {
display: table-header-group;
}
}
By including this CSS, when a user hits Ctrl+P (or Cmd+P), their browser's print preview will instantly transform the webpage into a perfectly formatted document, ready to be saved as a PDF.
The Browser is Your PDF Engine: Headless Automation
Manually printing to PDF is great for users, but what about automated server-side generation? This is where headless browsers come in. Tools like Puppeteer (for Chrome) and Playwright (for multiple browsers) allow you to control a real browser programmatically from your server.
The workflow is beautiful in its elegance:
- Create a route in your web application that renders the HTML document (e.g.,
/invoices/123/print-view
). - Write a simple script that tells the headless browser to visit that URL.
- Tell the browser to export the page as a PDF.
Here's a quick example using Puppeteer in Node.js:
const puppeteer = require('puppeteer');
async function createPdf(url, outputPath) {
const browser = await puppeteer.launch();
const page = await browser.newPage();
// 1. Go to the page
await page.goto(url, { waitUntil: 'networkidle0' });
// 2. Generate the PDF
await page.pdf({
path: outputPath,
format: 'A4',
printBackground: true, // Crucial for including background colors/images
margin: { top: '2cm', right: '2cm', bottom: '2cm', left: '2cm' }
});
await browser.close();
console.log(`PDF generated at ${outputPath}`);
}
createPdf('http://yourapp.com/invoices/123', 'invoice-123.pdf');
You're using Google Chrome's world-class rendering engine to create your PDF. The result is a high-fidelity document that perfectly matches what you designed with your CSS.
When to Stick with a PDF Library
Is this CSS-based approach a silver bullet? Not always. There are still valid reasons to use a traditional PDF library. Here's a quick comparison:
Use Modern CSS + Headless Browser When... | Stick with a PDF Library When... |
---|---|
You already have the document as a web page. | You need to generate a PDF from non-web data without an HTML view. |
You value maintainability and a single source of truth. | You require PDF-specific features like fillable forms, digital signatures, or advanced encryption. |
Your layout is complex but achievable with modern CSS (Flexbox/Grid). | You need to merge or manipulate existing PDF files. |
Development speed and leveraging existing skills are priorities. | You have strict regulatory requirements (e.g., PDF/A compliance) that browsers can't guarantee. |
Conclusion: A Paradigm Shift
For a huge percentage of everyday document generation tasks—invoices, reports, resumes, tickets, and statements—ditching dedicated PDF libraries in favor of modern CSS is a clear win. It simplifies your stack, reduces maintenance overhead, and empowers you to use the web development skills you've already honed.
By treating a PDF as just another output target for your HTML, you embrace the true power and flexibility of the web platform. The next time you're asked to generate a PDF, don't automatically reach for a new dependency. First, ask yourself: can I just build a webpage and print it? The answer, more often than not, is a resounding yes.