Web Development

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.

A

Alex Miller

A front-end developer passionate about web standards, performance, and vanilla JavaScript solutions.

7 min read15 views

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:

  1. When a user clicks "Download PDF," we'll use CSS to visually hide every single element on the page.
  2. We'll then make an exception and *un-hide* only the specific Web Component we want to turn into a PDF.
  3. 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.

Advertisement

Step 1: Isolating Your Web Component for Print

First, let's assume we have a simple Web Component, like an `` card. The component itself can be anything, but it's crucial that we can target it.

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 `