reportgen
Back to all posts

How to Make Your Reports Less Ugly - PDF Styling with reportgen.io πŸ’…

January 28, 2025

Generating PDFs with reportgen.io is a powerful way to automate reports, invoices, and data visualizations. But if your PDFs look like they were made in 1998, it's time for a styling upgrade.

In this guide, we’ll explore:

  • βœ… Styling PDFs using standard CSS for complete control.
  • βœ… Using Tailwind CSS as an alternative for rapid styling.
  • βœ… Adding Charts with Chart.js for dynamic data visualization.

Whether you stick to vanilla CSS or integrate third-party libraries, both approaches are valid depending on your workflow and preferences.

Note: Animations, hover effects, and transitions will not work in PDFs. Stick to static styling elements.


πŸ“œ 1. Styling with Basic CSS

Let’s start with plain CSS - a tried-and-true method that gives you full flexibility.

Basic Invoice with Standard CSS

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Invoice</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            padding: 20px;
            background: #f9f9f9;
        }
        .invoice-container {
            max-width: 600px;
            margin: auto;
            background: white;
            border: 1px solid #ddd;
            padding: 20px;
            box-shadow: 2px 2px 10px rgba(0, 0, 0, 0.1);
        }
        .invoice-header {
            text-align: center;
            font-size: 24px;
            font-weight: bold;
            margin-bottom: 20px;
        }
        .invoice-items {
            width: 100%;
            border-collapse: collapse;
        }
        .invoice-items th, .invoice-items td {
            border: 1px solid #ddd;
            padding: 10px;
            text-align: left;
        }
        .invoice-items th {
            background: #f4f4f4;
        }
        .total {
            text-align: right;
            font-weight: bold;
            margin-top: 10px;
        }
    </style>
</head>
<body>
    <div class="invoice-container">
        <div class="invoice-header">Invoice</div>
        <table class="invoice-items">
            <thead>
                <tr>
                    <th>Item</th>
                    <th>Qty</th>
                    <th>Price</th>
                </tr>
            </thead>
            <tbody>
                <tr>
                    <td>Web Development</td>
                    <td>1</td>
                    <td>$500</td>
                </tr>
                <tr>
                    <td>Hosting</td>
                    <td>1</td>
                    <td>$100</td>
                </tr>
            </tbody>
        </table>
        <p class="total">Total: $600</p>
    </div>
</body>
</html>

βœ… Pros of Using Basic CSS

  • Full control over styling, without relying on external libraries.
  • Works universally without extra dependencies.
  • No need for external scripts, making it lighter for PDF rendering.

❌ Cons of Using Basic CSS

  • More manual styling needed (spacing, typography, colors).
  • Requires structuring stylesheets and keeping them maintainable.

πŸš€ 2. Using Tailwind CSS for Faster Styling

If you prefer utility-first styling, Tailwind CSS can make things easier - especially if you already use it in your web app.

Tailwind-Powered Invoice

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Invoice</title>
    <script src="https://cdn.tailwindcss.com"></script>
</head>
<body class="bg-gray-100 p-8">
    <div class="max-w-xl mx-auto bg-white p-6 rounded-lg shadow-md">
        <h1 class="text-2xl font-bold text-center mb-4">Invoice</h1>
        <table class="w-full border-collapse">
            <thead>
                <tr class="bg-gray-200 text-left">
                    <th class="p-2 border">Item</th>
                    <th class="p-2 border">Qty</th>
                    <th class="p-2 border">Price</th>
                </tr>
            </thead>
            <tbody>
                <tr class="border-t">
                    <td class="p-2 border">Web Development</td>
                    <td class="p-2 border text-center">1</td>
                    <td class="p-2 border">$500</td>
                </tr>
                <tr class="border-t">
                    <td class="p-2 border">Hosting</td>
                    <td class="p-2 border text-center">1</td>
                    <td class="p-2 border">$100</td>
                </tr>
            </tbody>
        </table>
        <p class="text-right font-bold text-lg mt-4">Total: $600</p>
    </div>
</body>
</html>

βœ… Pros of Using Tailwind

  • Speeds up development with utility classes.
  • Keeps styles inline, reducing CSS bloat.
  • Great for teams already using Tailwind in their web projects.

❌ Cons of Using Tailwind

  • Requires loading an external script, which must be inlined for PDFs.
  • Can be overwhelming for beginners.

πŸ“Š 3. Adding Dynamic Charts with Chart.js

Adding charts to your PDFs can make your reports more visually appealing and data-driven. With Chart.js, you can embed live charts directly inside your report templates - no need for image conversion!

Let’s see how to:

  • βœ… Add a basic chart to your PDF.
  • βœ… Template the chart data dynamically using EJS (or any other templating engine).

πŸ“Œ Example 1: Static Chart

If you don’t need dynamic data, here’s how you can embed a simple bar chart inside your PDF template:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Chart Example</title>
    <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
</head>
<body>
    <h2>Sales Performance</h2>
    <canvas id="salesChart"></canvas>
    <script>
        const ctx = document.getElementById('salesChart').getContext('2d');
        new Chart(ctx, {
            type: 'bar',
            data: {
                labels: ['January', 'February', 'March'],
                datasets: [{
                    label: 'Revenue ($)',
                    data: [1200, 900, 1500],
                    backgroundColor: ['#1E40AF', '#1D4ED8', '#3B82F6']
                }]
            }
        });
    </script>
</body>
</html>

βœ… Why does this work in PDFs?

  • Chart.js renders as a static chart inside the HTML, meaning it will be included in the PDF exactly as you see it in the browser.
  • No need to convert it to an image - just write HTML + JavaScript, and reportgen.io handles the rest.

πŸ“Œ Example 2: Dynamic Chart with Templating (EJS)

Now, let’s say you want your chart to display different data dynamically - maybe based on an API response or user input.

Using EJS templating, we can pass dynamic data to the chart inside our report template.

πŸ“ Dynamic Chart using EJS

<!DOCTYPE html>
<html lang='en'>
<head>
  <meta charset='UTF-8'>
  <meta name='viewport' content='width=device-width, initial-scale=1.0'>
  <title>Monthly Sales Report</title>
  <script src='https://cdn.jsdelivr.net/npm/chart.js'></script>
  <script src='https://cdn.tailwindcss.com'></script>
</head>
<body class='bg-gray-100 text-gray-900 flex flex-col items-center p-8'>
  <h2 class='text-3xl font-bold text-blue-600 mb-4'>Monthly Sales Report</h2>
  <div class='bg-white shadow-lg rounded-lg p-6 w-full max-w-2xl'>
    <canvas id='salesChart'></canvas>
  </div>
 
  <script>
    document.addEventListener('DOMContentLoaded', function () {
      const months = <%- JSON.stringify(months) %>;
      const revenue = <%- JSON.stringify(revenue) %>;
      const colors = months.map((_, i) => `hsl(${i * 40}, 70%, 50%)`);
 
      const ctx = document.getElementById('salesChart').getContext('2d');
      new Chart(ctx, {
        type: 'bar',
        data: {
          labels: months,
          datasets: [{
            label: 'Revenue ($)',
            data: revenue,
            backgroundColor: colors,
            borderColor: colors.map(color => color.replace('50%', '40%')),
            borderWidth: 1
          }]
        },
        options: {
          responsive: true,
          scales: {
            y: { beginAtZero: true }
          }
        }
      });
    });
  </script>
</body>
</html>

πŸš€ How It Works:

  • <%= JSON.stringify(months) %> injects an array of month names dynamically.
  • <%= JSON.stringify(revenue) %> injects revenue data dynamically.
  • When generating the report, you can pass different values for months and revenue, making the chart fully customizable per report.

When calling the reportgen.io API, you pass the chart data dynamically like this:

{
  "html_template": "<!DOCTYPE html>\n<html lang='en'>\n<head>\n  <meta charset='UTF-8'>\n  <meta name='viewport' content='width=device-width, initial-scale=1.0'>\n  <title>Monthly Sales Report</title>\n  <script src='https://cdn.jsdelivr.net/npm/chart.js'></script>\n  <script src='https://cdn.tailwindcss.com'></script>\n</head>\n<body class='bg-gray-100 text-gray-900 flex flex-col items-center p-8'>\n  <h2 class='text-3xl font-bold text-blue-600 mb-4'>Monthly Sales Report</h2>\n  <div class='bg-white shadow-lg rounded-lg p-6 w-full max-w-2xl'>\n    <canvas id='salesChart'></canvas>\n  </div>\n  \n  <script>\n    document.addEventListener('DOMContentLoaded', function () {\n      const months = <%- JSON.stringify(months) %>;\n      const revenue = <%- JSON.stringify(revenue) %>;\n      const colors = months.map((_, i) => `hsl(${i * 40}, 70%, 50%)`);\n      \n      const ctx = document.getElementById('salesChart').getContext('2d');\n      new Chart(ctx, {\n        type: 'bar',\n        data: {\n          labels: months,\n          datasets: [{\n            label: 'Revenue ($)',\n            data: revenue,\n            backgroundColor: colors,\n            borderColor: colors.map(color => color.replace('50%', '40%')),\n            borderWidth: 1\n          }]\n        },\n        options: {\n          responsive: true,\n          scales: {\n            y: { beginAtZero: true }\n          }\n        }\n      });\n    });\n  </script>\n</body>\n</html>",
  "data": {
    "months": ["January", "February", "March"],
    "revenue": [1200, 900, 1500]
  },
  "engine": "ejs"
}
 

🎯 Result:

  • βœ… Your PDF will automatically update the chart with the new data.
  • βœ… You can reuse the same template for different reports - just send new values.

πŸ“Œ Final Thoughts

  • βœ… Chart.js works natively in PDFs - no need for extra processing.
  • βœ… You can use templating (like EJS) to pass dynamic data.
  • βœ… Works great for reports, dashboards, and analytics in PDF format.

With Chart.js + reportgen.io, your reports won’t just be readable - they’ll be data-driven and visually stunning! πŸš€


πŸ“Œ Conclusion

  • βœ… Use Basic CSS for full control and lightweight styling.
  • βœ… Use Tailwind CSS if you want quicker development with utility classes.
  • βœ… Use Chart.js if you need charts in your PDF.

Next Steps:

Now go make those PDFs look stunning! πŸš€