DEV Community


Posted on

How to Generate Stylish PDFs and Images with Tailwind CSS in Your Application

Transform your application into a powerhouse for creating stunning documents and images using HTML styled with Tailwind CSS. In this guide, we’ll walk you through the process of integrating a powerful document generation API from, showcasing how you can enhance your application's functionality with high-quality, styled document outputs.

Why Use Tailwind CSS for Document Generation?

Tailwind CSS is renowned for its utility-first approach, making it an excellent choice for developers looking to implement custom styles quickly without leaving the comfort of their HTML. By using Tailwind CSS with the API, developers can ensure that their digital documents carry the same level of precision and customisation as their web interfaces.

Integrating the API

Our chosen API isn't just any document generation tool; it excels by accepting HTML content styled using Tailwind CSS, allowing full utilization of Tailwind's utility classes directly within your documents. Here’s a brief overview of the API capabilities that you can leverage:

  • HTML Content: Embed HTML with Tailwind CSS for dynamic styling.

  • Format Specification: Supports various paper sizes such as A4, Letter, etc.

  • Output Options: Generate documents in PDF, PNG, JPEG, or WebP formats.

  • Custom Dimensions: Control output dimensions and scaling to ensure your documents look perfect.

Below is a basic React component setup that utilises the API to generate a document:

import { useState } from 'react';

const sampleHtml = `
<figure class="md:flex bg-slate-100 rounded-xl p-8 md:p-0 dark:bg-slate-800">
  <img class="w-24 h-24 md:w-48 md:h-auto md:rounded-none rounded-full mx-auto object-cover" src="" alt="" width="384" height="512">
  <div class="pt-6 md:p-8 text-center md:text-left space-y-4">
      <p class="text-lg font-medium">
        “Tailwind CSS is the only framework that I've seen scale
        on large teams. It’s easy to customize, adapts to any design,
        and the build size is tiny.”
    <figcaption class="font-medium">
      <div class="text-sky-500 dark:text-sky-400 font-bold">
        Sarah Dayan
      <div class="text-slate-700 dark:text-slate-500">
        Staff Engineer, Algolia

function App() {
  const [error, setError] = useState('');
  const [downloading, setDownloading] = useState(false);
  const [payload, setPayload] = useState<BodyPayload>({
    html: sampleHtml,
    output: 'pdf',

  const setHtml = (htmlContent: string) => {
    setPayload({ ...payload, html: htmlContent });

  const setOutput = (outputFormat: BodyPayload["output"]) => {
    setPayload({ ...payload, output: outputFormat });

  const download = async () => {
    try {
      const response = await requestDownload(payload);
      if (response.error) {
      } else if (response.requestId) {
    } catch (error) {
      setError('Something went wrong.');

  return (
      <SelectOutput onChange={setOutput} />
      <button onClick={download}>{downloading ? 'Downloading...' : `Download .${payload.output}`}</button>
      {error && <p>{error}</p>}
        onChange={(e) => setHtml(}

export default App;
Enter fullscreen mode Exit fullscreen mode

Send a POST request with your styled HTML content to generate the desired file format:

const apiUrl = ``;

async function requestDownload(payload: BodyPayload) {
  const response = await fetch(apiUrl + '/request', {
    method: 'POST',
    body: JSON.stringify(payload),
    headers: { 'Content-Type': 'application/json' },
  return await response.json();
Enter fullscreen mode Exit fullscreen mode

Handle download attempts and retries:

const RETRY_INTERVAL_MS = 2500;

async function downloadWithRetry(requestId: string) {
  const intervalId = setInterval(async () => {
    const response = await fetch(`${apiUrl}/request/${requestId}/download`);
    if (response.ok) {
      const blob = await response.blob();
    } else {
      console.error('Download failed, retrying...');
Enter fullscreen mode Exit fullscreen mode

Document generation might take 2-6 second. This retry logic ensures we download the file when it's ready.

You might ask: what are the options I can pass to the api? Well... here is the typescript definition my friend:

type BodyPayload = {
  html?: string; // must be undefined if 'template' prop is used
  format?: // applicable only for pdf, default a4
    | "LETTER"
    | "LEGAL"
    | "TABLOID"
    | "LEDGER"
    | "A0"
    | "A1"
    | "A2"
    | "A3"
    | "A4"
    | "A5"
    | "A6"
    | "Letter"
    | "Legal"
    | "Tabloid"
    | "Ledger"; 
  output?: "pdf" | "png" | "jpeg" | "webp"; // default pdf
  size?: {
    scale?: number; // default 2
    width?: string | number; // default 210
    height?: string | number; // default 297
    unit?: "px" | "in" | "cm" | "mm"; // default mm
  template?: { 
    html: string; // handlebars dynamic html 
    data: Record<string, any>; // data for dynamic html
Enter fullscreen mode Exit fullscreen mode


Integrating Tailwind CSS with the API in your application not only enhances the consistency between web and printable formats but also simplifies the process of generating custom-styled documents. Start utilizing this integration today and take your application's functionality to the next level. For more information visit

Happy coding!

Top comments (0)