Create PDFs with Tailwind
Tags: tailwindcss, pdf, javascript
Published: 2024-03-21 23:23:54


As a developer, creating PDFs seems like a simple task. We build frontends with modern toolkit in just hours, how could static documents be any harder?

PDF was invented in 1993 by Adobe as a cross-platform document format. The format itself focuses on being portable rather than interactive - an orthogonal approach to HTML and CSS. While the latter defines a box model, the former has an imperative approach. In a nutshell, an HTML rectangle is a set of 4 lines in PDF.

This approach is the strength of PDF and the reason it is so widely used: it always looks the same. HTML on the other hand relies on many factors from screen size to browser version. What if we could bring the layout capacities of HTML to PDF?

## Create your first PDF with React and Tailwind

The open-source library [react-print-pdf](https://github.com/OnedocLabs/react-print-pdf) brings a set of components and wrappers we can use to build beautiful PDFs in minutes.

### Compile to HTML

The challenge with Tailwind is that it is a dynamic CSS framework. It relies on a JavaScript runtime to generate the final CSS. This is a problem for PDF generation, as we require a static file. We will first convert to static HTML, then to PDF.

For this, we will use the `<Tailwind>` component from `react-print-pdf`. This component will detect all the Tailwind classes and generate the final CSS. We can then use the `compile` function to convert the React tree to HTML.

```tsx
import { Tailwind, Footnote, compile } from "@fileforge/react-print";

const getHTML = async () => {
  return compile(
    <Tailwind>
      <h1 className="text-4xl font-bold">
        Invoice #123
        <Footnote>
          <p className="text-sm">This is a demo invoice</p>
        </Footnote>
      </h1>
    </Tailwind>
  );
};
```

When calling `getHTML`, we will get the following HTML:

```html
<!doctype html>
<html>
  <head>
    <style>
      .text-4xl {
        font-size: 2.25rem;
        line-height: 2.5rem;
      }
      .font-bold {
        font-weight: 700;
      }
      .text-sm {
        font-size: 0.875rem;
        line-height: 1.25rem;
      }
    </style>
  </head>
  <body>
    <h1 class="text-4xl font-bold">
      Invoice #123
      <div class="footnote">
        <p class="text-sm">This is a demo invoice</p>
      </div>
    </h1>
  </body>
</html>
```

### Converting the HTML to PDF

There are several ways to convert this HTML to a PDF:

- Use Fileforge as a client-side or server-side API, that will support all features such as headers, footers, and page numbers.
- If on the client side, you can use [`react-to-print`](https://github.com/MatthewHerbst/react-to-print) to use the browser's print dialog. This is cheap option but will not support advanced features and may introduce a lot of visual bugs.
- Use a server-side headless browser such as [`puppeteer`](https://pptr.dev/api/puppeteer.page.pdf) to convert the HTML to PDF. This is the most reliable free option, but requires a server. If you need to use it in production, we recommend you use [Gotenberg](https://github.com/gotenberg/gotenberg).

Here is an example on how to convert the HTML to PDF using Fileforge:

```tsx
import { FileforgeClient } from "@fileforge/client";
import { getHTML } from "./getHTML";
import fs from "fs";

const ff = new FileforgeClient({
  apiKey: process.env.FILEFORGE_API_KEY
});

const { file } = await ff.pdf.generate(await getHTML());

file.pipe(fs.createWriteStream("invoice.pdf");
```

That's it! You now have a beautiful PDF invoice generated from your React app. You can use most of Tailwind features as well as the components from `react-print-pdf` to create advanced layouts. [Check out the documentation](https://docs.fileforge.com/introduction) for more information.

## Under the hood: dynamic styles

If you have made it this far, you may be wondering how the dynamic styles are converted to static CSS. Tailwind brings a specific set of challenges as it is a utility-first framework. Let's have a look at the usual Tailwind generation process:

1. Tailwind parses the files specified in your `tailwind.config.js` and generates a set of classes.
2. It then uses a PostCSS plugin to replace the classes in your CSS with the actual styles.
3. It then uses a JavaScript runtime to generate the final CSS, deduplicating and minifying it.

This works fine as a build tool, but bringing just-in-time compilation to PDF generation is a challenge. On serverless and browser environments, there is no file system and we need to detect dynamic classes in the React tree.

The approach that the Tailwind component uses is similar to the one in [`react-email`](https://react.email/docs/components/tailwind). The Tailwind React component parses its children tags and detects the classes. It then uses a browserified version of Tailwind to generate the final CSS. This is then injected in the HTML. Not as easy as it sounds!

This blog post was originally posted on the [Fileforge blog](https://www.fileforge.com/blog/react-tailwind-dynamic-pdf).