HTML & CSS Tips for Better PDFs
Master print styles with our Print CSS Cheatsheet. Learn tips, tricks, and code examples to create professional, print-ready HTML documents.
Adding page numbers and footers to PDF documents is one of the most common challenges when converting HTML to PDF. Unlike web pages that scroll infinitely, PDF documents are paginated โ and users expect professional documents to include page numbers, total page counts, and footer information on every page. However, HTML and CSS were not originally designed with print pagination in mind, which makes this surprisingly difficult.
In this comprehensive guide, we'll explore 6 different approaches to implementing page numbers and footers in HTML-to-PDF conversion. Each method has its own strengths, weaknesses, and ideal use cases. We'll provide interactive examples using CustomJS so you can test each approach and see the results in real-time.
The most elegant and standards-compliant approach is using CSS Paged Media with @page
rules and the counter(page)
function. This CSS feature was specifically designed for print layouts and allows you to place content in page margin boxes like @bottom-center
, @bottom-right
, etc.
CSS defines special page margin boxes where you can place running content that appears on every page. You can access the current page number with counter(page)
and the total page count with counter(pages)
.
The generated PDF will be displayed here.
counter(pages)
for total page countIdeal when you're using PDF engines like PrinceXML or WeasyPrint and need a clean, CSS-only solution. Not recommended for Chrome-based solutions unless you only need current page numbers (not total pages).
A simple but effective approach is to use CSS position: fixed
with bottom: 0
to create a footer that appears on every printed page. This works across all browsers and PDF engines, making it the most compatible solution.
By setting a footer element to position: fixed
within a @media print
block, the footer automatically repeats on every page during PDF generation. You add manual page numbers or use JavaScript to inject them.
The generated PDF will be displayed here.
Perfect for simple footers with static content (company name, document title, date). Also great when combined with JavaScript for dynamic page numbers. This is the most reliable approach for Chrome-based PDF generation.
This approach combines fixed-position footers with JavaScript to dynamically inject page numbers. Before generating the PDF, JavaScript calculates the total number of pages and inserts "Page X of Y" text into footer elements.
The HTML includes placeholder footer elements on each page. JavaScript runs before PDF generation to: 1) Calculate total pages by measuring document height vs page height 2) Insert dynamic page numbers into each footer placeholder 3) Then the PDF is generated with all page numbers in place
The generated PDF will be displayed here.
Ideal for Chrome-based PDF generation where you need accurate "Page X of Y" footers. Perfect when using CustomJS or Puppeteer, as you can execute the JavaScript before PDF generation.
An advanced CSS Paged Media technique using named pages and running elements. This allows you to define different headers/footers for different sections of your document (e.g., different footer for title page vs content pages).
You define named page types using @page chapter
, @page cover
, etc. Then assign elements to specific pages with page: chapter;
in CSS. Each named page can have its own header/footer configuration.
The generated PDF will be displayed here.
Best for complex documents like books, manuals, or reports with multiple sections that need different pagination styles. Only viable when using PrinceXML or similar professional PDF engines.
This creative approach uses HTML tables with thead
and tfoot
elements that automatically repeat on every page. By placing footer content in <tfoot>
, browsers will repeat it at the bottom of each printed page.
You wrap your entire document content in a table. The <tfoot>
element contains your footer, and browsers automatically repeat <thead>
and <tfoot>
on every page when a table spans multiple pages during printing.
The generated PDF will be displayed here.
<thead>
Useful for tabular reports or invoices where table structure makes sense. Good fallback option when you need repeating footers without JavaScript but can't use position:fixed or @page rules.
CustomJS offers built-in pagination features that automatically handle page numbers and footers for you. This is the most developer-friendly approach, combining the best aspects of all previous methods while handling the complexity behind the scenes.
CustomJS provides special configuration options when generating PDFs. You can specify footer templates with variables like and
, and CustomJS automatically injects the correct values on every page.
The generated PDF will be displayed here.
Perfect for production applications where you need reliable, professional pagination without dealing with browser quirks or JavaScript complexity. Ideal when you're already using CustomJS for HTML-to-PDF conversion.
Here's a comprehensive comparison of all six approaches to help you choose the right one for your use case:
Approach | Complexity | Page Numbers | Chrome Support | Best For |
---|---|---|---|---|
1. CSS @page | Low | โ Automatic | โ ๏ธ Partial | PrinceXML, WeasyPrint |
2. Fixed Position | Low | โ Manual | โ Full | Static footers |
3. JavaScript | Medium | โ Dynamic | โ Full | Chrome-based, Puppeteer |
4. Named Pages | High | โ Section-specific | โ None | Complex documents, books |
5. Table-Based | Low | โ Manual | โ Good | Reports, invoices |
6. CustomJS | Very Low | โ Automatic | โ Perfect | Production apps |
โ Choose Approach 1 (CSS @page) for the cleanest, most standards-compliant solution.
โ Choose Approach 2 (Fixed Position) for maximum compatibility and simplicity.
โ Choose Approach 3 (JavaScript) for full control with "Page X of Y" support.
โ Choose Approach 6 (CustomJS) to avoid all the complexity and get professional results immediately.
โ Choose Approach 4 (Named Pages) if you're committed to PrinceXML and need section-specific pagination.
โ Choose Approach 5 (Table-Based) for invoices and reports where table structure is natural.
For most modern web applications generating PDFs from HTML, we recommend CustomJS (Approach 6). It provides the best developer experience, most reliable output, and eliminates all the browser compatibility headaches. The built-in pagination features are production-ready and handle edge cases that would take weeks to solve manually.
If you need a free, self-hosted solution, use Approach 3 (JavaScript) with Chrome Headless or Puppeteer. It requires more code but gives you full control and works reliably.
Avoid Approach 4 (Named Pages) unless you're already invested in PrinceXML, as the limited browser support makes it impractical for most use cases.
Continue reading on similar topics
Master print styles with our Print CSS Cheatsheet. Learn tips, tricks, and code examples to create professional, print-ready HTML documents.
Compare HTML to PDF conversion APIs for price, speed, and JavaScript support. CustomJS offers 600 free PDFs/month and easy Make.com integration.