Comprehensive guide to handling tables in PDF generation with smart pagination, header repetition, and row splitting prevention.
The PDF generator provides advanced table handling capabilities:
- Header Repetition - Table headers automatically repeat on each page
- Row Splitting Prevention - Keeps table rows together across pages
- Auto-borders - Enforce borders for better PDF visibility
- Column Width Fixing - Consistent column widths across pages
- Text Wrapping - Smart text wrapping in table cells
- Table Splitting - Intelligently split large tables across pages
import { generatePDF } from '@encryptioner/html-to-pdf-generator';
const element = document.getElementById('content');
await generatePDF(element, 'document.pdf', {
repeatTableHeaders: true,
avoidTableRowSplit: true,
});Repeat table headers (<thead>) on each page.
repeatTableHeaders: true // Repeat headers (default: true)When enabled, <thead> content appears at the top of each page where the table continues.
Prevent table rows from splitting across pages.
avoidTableRowSplit: true // Keep rows together (default: true)Ensures each <tr> stays on a single page. If a row doesn't fit, it moves to the next page.
Use semantic HTML for best results:
<table>
<thead>
<tr>
<th>Product</th>
<th>Price</th>
<th>Quantity</th>
<th>Total</th>
</tr>
</thead>
<tbody>
<tr>
<td>Widget A</td>
<td>$10.00</td>
<td>5</td>
<td>$50.00</td>
</tr>
<tr>
<td>Widget B</td>
<td>$15.00</td>
<td>3</td>
<td>$45.00</td>
</tr>
</tbody>
<tfoot>
<tr>
<td colspan="3">Total:</td>
<td>$95.00</td>
</tr>
</tfoot>
</table>await generatePDF(element, 'invoice.pdf', {
repeatTableHeaders: true,
avoidTableRowSplit: true,
});table {
width: 100%;
border-collapse: collapse;
}
thead {
background-color: #f3f4f6;
font-weight: bold;
}
th, td {
padding: 12px;
text-align: left;
border: 1px solid #d1d5db;
}
tbody tr:nth-child(even) {
background-color: #f9fafb;
}Headers automatically repeat when tables span multiple pages:
<table>
<thead>
<tr>
<th>Date</th>
<th>Description</th>
<th>Amount</th>
</tr>
</thead>
<tbody>
<!-- 100 rows of data -->
<tr><td>2024-01-01</td><td>Payment</td><td>$100</td></tr>
<!-- ... more rows ... -->
</tbody>
</table>await generatePDF(element, 'report.pdf', {
repeatTableHeaders: true, // Headers appear on every page
});Complex headers with multiple rows:
<thead>
<tr>
<th rowspan="2">Product</th>
<th colspan="2">Q1</th>
<th colspan="2">Q2</th>
</tr>
<tr>
<th>Units</th>
<th>Revenue</th>
<th>Units</th>
<th>Revenue</th>
</tr>
</thead>Both header rows repeat on each page automatically.
await generatePDF(element, 'document.pdf', {
avoidTableRowSplit: true, // Rows stay on single page
});Behavior:
- If a row fits on current page → rendered on current page
- If a row doesn't fit → moved to next page entirely
- No partial row content on page boundaries
For very large rows (taller than a page), the row will be split but cell content is kept together:
<tr>
<td>Product Name</td>
<td>
<div style="height: 400mm;">
<!-- Very tall content -->
</div>
</td>
</tr>Behavior: Content splits at natural boundaries, but cell integrity is maintained.
Use explicit widths for consistent columns:
<table style="table-layout: fixed;">
<colgroup>
<col style="width: 40%;">
<col style="width: 30%;">
<col style="width: 30%;">
</colgroup>
<thead>
<tr>
<th>Description</th>
<th>Quantity</th>
<th>Price</th>
</tr>
</thead>
<tbody>
<!-- rows -->
</tbody>
</table>Use percentage widths for flexibility:
table {
width: 100%;
}
th:nth-child(1), td:nth-child(1) { width: 50%; }
th:nth-child(2), td:nth-child(2) { width: 25%; }
th:nth-child(3), td:nth-child(3) { width: 25%; }Borders are automatically enforced for PDF clarity:
table {
border-collapse: collapse;
}
th, td {
border: 1px solid #000; /* Clear borders for PDF */
}Alternating row colors for readability:
tbody tr:nth-child(odd) {
background-color: #ffffff;
}
tbody tr:nth-child(even) {
background-color: #f3f4f6;
}Adequate padding improves readability:
th, td {
padding: 8px 12px;
}td {
word-wrap: break-word;
overflow-wrap: break-word;
hyphens: auto;
}For columns that should stay on one line:
.no-wrap {
white-space: nowrap;
}<td class="no-wrap">SKU-12345</td>.truncate {
max-width: 200px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}<table>
<tr>
<td>
Parent Cell
<table>
<tr>
<td>Nested Cell 1</td>
<td>Nested Cell 2</td>
</tr>
</table>
</td>
</tr>
</table>Note: Keep nesting shallow (1-2 levels) for best results.
<table>
<tr>
<td><img src="product.jpg" alt="Product" width="100"></td>
<td>Product Description</td>
<td>$29.99</td>
</tr>
</table>await generatePDF(element, 'catalog.pdf', {
repeatTableHeaders: true,
optimizeImages: true,
});<tr class="highlight">
<td>Important Item</td>
<td>$999.99</td>
</tr>tr.highlight {
background-color: #fef3c7;
font-weight: bold;
}For tables with many rows (100+):
await generatePDF(element, 'large-table.pdf', {
repeatTableHeaders: true,
avoidTableRowSplit: true,
showPageNumbers: true,
compress: true,
});// For very large tables (1000+ rows)
await generatePDF(element, 'huge-table.pdf', {
scale: 1.5, // Reduce scale for faster generation
compress: true,
});<tfoot>
<tr>
<td colspan="3">Grand Total:</td>
<td>$1,234.56</td>
</tr>
</tfoot>Behavior: Footers appear at the end of the table only (not repeated like headers).
To repeat footers on each page, include them in both <thead> and <tfoot>:
<table>
<thead>
<tr class="header-row">
<th>Item</th>
<th>Price</th>
</tr>
</thead>
<tbody>
<!-- data rows -->
</tbody>
<tfoot>
<tr class="footer-row">
<td colspan="2">Page Total: Calculate manually</td>
</tr>
</tfoot>
</table>Problem: Table headers don't repeat on new pages.
Solutions:
- Ensure
repeatTableHeaders: true - Use proper
<thead>structure - Check that CSS doesn't hide thead
await generatePDF(element, 'document.pdf', {
repeatTableHeaders: true, // Must be enabled
});Problem: Table rows are split across page boundaries.
Solutions:
- Enable
avoidTableRowSplit: true - Reduce row height if possible
- Check CSS that might force row height
await generatePDF(element, 'document.pdf', {
avoidTableRowSplit: true,
});Problem: Column widths vary across pages.
Solutions:
- Use
table-layout: fixed - Specify explicit column widths
- Use colgroup to define columns
table {
table-layout: fixed;
width: 100%;
}Problem: Table borders don't appear in PDF.
Solutions:
- Use explicit border styles
- Avoid border-collapse issues
- Use solid borders (not dashed/dotted)
table {
border-collapse: collapse;
}
td, th {
border: 1px solid #000;
}Always use proper table structure:
<table>
<thead><!-- Headers --></thead>
<tbody><!-- Data --></tbody>
<tfoot><!-- Footers --></tfoot>
</table>Use consistent styling across all tables:
/* Global table styles */
table {
width: 100%;
border-collapse: collapse;
margin-bottom: 20px;
}
th, td {
padding: 10px;
border: 1px solid #ddd;
text-align: left;
}
thead {
background-color: #f5f5f5;
font-weight: bold;
}Test with realistic data volumes:
// Test with small dataset
const smallTable = createTable(10);
await generatePDF(smallTable, 'small.pdf');
// Test with large dataset
const largeTable = createTable(500);
await generatePDF(largeTable, 'large.pdf');For web version, use responsive tables; for PDF, use fixed layout:
<!-- Web version -->
<div class="table-responsive">
<table><!-- content --></table>
</div>
<!-- PDF version (remove wrapper) -->
<table><!-- content --></table><table class="invoice-table">
<thead>
<tr>
<th>Item</th>
<th>Description</th>
<th>Qty</th>
<th>Price</th>
<th>Total</th>
</tr>
</thead>
<tbody>
<tr>
<td>001</td>
<td>Professional Services</td>
<td>10</td>
<td>$150.00</td>
<td>$1,500.00</td>
</tr>
<!-- more rows -->
</tbody>
<tfoot>
<tr>
<td colspan="4">Subtotal:</td>
<td>$1,500.00</td>
</tr>
<tr>
<td colspan="4">Tax (10%):</td>
<td>$150.00</td>
</tr>
<tr>
<td colspan="4"><strong>Total:</strong></td>
<td><strong>$1,650.00</strong></td>
</tr>
</tfoot>
</table>await generatePDF(invoiceElement, 'invoice.pdf', {
repeatTableHeaders: true,
avoidTableRowSplit: true,
format: 'a4',
showPageNumbers: true,
});<table class="data-report">
<thead>
<tr>
<th>Date</th>
<th>Metric</th>
<th>Value</th>
<th>Change</th>
</tr>
</thead>
<tbody>
<!-- 200+ rows of data -->
</tbody>
</table>await generatePDF(reportElement, 'monthly-report.pdf', {
repeatTableHeaders: true,
avoidTableRowSplit: true,
showPageNumbers: true,
compress: true,
});<table class="comparison-table">
<thead>
<tr>
<th>Feature</th>
<th>Basic</th>
<th>Pro</th>
<th>Enterprise</th>
</tr>
</thead>
<tbody>
<tr>
<td>Storage</td>
<td>10 GB</td>
<td>100 GB</td>
<td>Unlimited</td>
</tr>
<!-- more features -->
</tbody>
</table>- Multi-Page Generation - Page splitting behavior
- Page Breaks - Control page breaking
- Best Practices - Overall best practices
- Options Reference - Complete options documentation