From Web Component to PDF: A Modern, Library-Free Guide
Learn how to convert any Web Component into a high-quality PDF directly in the browser, without external libraries. A modern, vanilla JS guide.
Alex Miller
A front-end developer passionate about web standards, performance, and vanilla JavaScript solutions.
Ever built a beautiful, self-contained Web Component—like a dynamic invoice, a report dashboard, or a printable certificate—and then hit a wall when you needed to export it as a PDF? You're not alone. The usual path involves reaching for a hefty JavaScript library, adding kilobytes to your bundle, and wrestling with APIs that never seem to work quite right with the Shadow DOM. But what if I told you there's a modern, clean, and library-free way to do it?
This guide will walk you through a powerful technique that leverages the browser's own capabilities to generate crisp, perfect PDFs from your Web Components. Let's dive in and reclaim control over our code.
Why Go Library-Free? The Modern Mindset
In a world of npm packages for everything, choosing to go *without* one can feel radical. But the benefits are compelling, especially for a task like PDF generation:
- Zero Dependencies: You're not adding another package to your `package.json`. No `npm install`, no vulnerability audits, no bundle size increase. Your app stays lean.
- Ultimate Control: You're not at the mercy of a library's rendering engine. You're using the browser's—the very same one that's already rendering your component perfectly. This means higher fidelity and fewer surprises.
- Future-Proof: Browser standards are more stable than the flavor-of-the-month library. This technique relies on fundamental CSS and JavaScript APIs that aren't going anywhere.
- Simplicity: While it might seem complex at first, the logic is straightforward. You're essentially just telling the browser, "When printing, hide everything else."
Of course, it's not a silver bullet. It requires a bit more manual CSS work, but the payoff in performance and control is often worth it.
The Core Concept: The Browser is Your PDF Engine
The magic behind this entire process is the browser's built-in Print functionality. For years, browsers have had a "Print to PDF" or "Save as PDF" option in their print dialog. We're going to hijack this feature for our own purposes.
The strategy is simple:
- When a user clicks "Download PDF," we'll use CSS to visually hide every single element on the page.
- We'll then make an exception and *un-hide* only the specific Web Component we want to turn into a PDF.
- Finally, we'll programmatically trigger the browser's print dialog.
The user sees a print preview containing only our component, selects "Save as PDF," and gets a perfect document. To the user, it feels like a direct PDF download. To the developer, it's just clever CSS.
Step 1: Isolating Your Web Component for Print
First, let's assume we have a simple Web Component, like an `
Here’s a basic example of our component:
// invoice-summary.js
class InvoiceSummary extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
}
connectedCallback() {
const customer = this.getAttribute('customer') || 'N/A';
const amount = this.getAttribute('amount') || '$0.00';
this.shadowRoot.innerHTML = `
<style>
:host {
display: block;
border: 1px solid #ccc;
padding: 24px;
border-radius: 8px;
font-family: sans-serif;
max-width: 400px;
}
h3 {
margin: 0 0 16px;
color: #333;
}
p {
margin: 4px 0;
}
</style>
<h3>Invoice Summary</h3>
<p><strong>Billed to:</strong> ${customer}</p>
<p><strong>Total Amount:</strong> ${amount}</p>
`;
}
}
customElements.define('invoice-summary', InvoiceSummary);
In our main HTML, we'll use this component and add a button to trigger the PDF generation. The key is to wrap our component in a container that we can easily target with CSS. Let's use a class, `.printable-area`.
<body>
<h1>My Application Dashboard</h1>
<p>Here is the latest invoice. You can download it as a PDF.</p>
<div class="printable-area">
<invoice-summary customer="Acme Corp" amount="$1,250.50"></invoice-summary>
</div>
<button id="download-pdf-btn">Download PDF</button>
<script src="invoice-summary.js"></script>
<script src="app.js"></script>
</body>
Step 2: Crafting Print-Specific Styles
This is where the real work happens. We'll use the `@media print` CSS at-rule. These styles are *only* applied when the page is being printed (or in our case, saved as a PDF).
In your main stylesheet (`styles.css`), add the following:
/* styles.css */
@media print {
/* Hide everything on the page by default */
body * {
visibility: hidden;
}
/* Make the printable area and its children visible */
.printable-area, .printable-area * {
visibility: visible;
}
/* Position the printable area at the top-left of the page */
.printable-area {
position: absolute;
left: 0;
top: 0;
width: 100%;
}
/* Hide the download button itself when printing */
#download-pdf-btn {
display: none;
}
}
This CSS is powerful. It says: "When a print command is issued, make everything invisible... *except* for the `.printable-area` and everything inside it." We also reposition the container to ensure it starts at the very top of the PDF page, removing any layout artifacts from the main page.
A Note on Shadow DOM
One common pitfall is that styles in the main document don't pierce the Shadow DOM. Our component's internal styles are safe. If you needed to *change* the component's internal styles *only* for printing (e.g., switch to a black-and-white theme), you would need to include a `@media print` block *inside* the component's own `