What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

For numbering headings in a wkhtmltopdf PDF, use CSS counters with generated content: reset a counter at the scope where it should start, increment it on the elements being numbered, and display it with counter() or counters(). For physical PDF page numbers, use wkhtmltopdf’s documented header or footer placeholders, [page] and [topage], rather than assuming CSS Paged Media page counters will work in your deployed binary.

How CSS counters work

A CSS counter is state maintained as the document’s boxes are laid out. Three properties do the main work:

  • counter-reset creates a counter or sets it back to a chosen value.
  • counter-increment changes the counter as an element is encountered.
  • counter() and counters() read the current value for generated content, usually in ::before or ::after.

For example, counter(section) prints one counter value. counters(section, ".") prints the nested values for a counter with the same name, separated by periods. Generated numbering is text produced by CSS; it does not add a number to the source HTML.

Counter scope matters

A reset applies within the counter scope established by the document and generated box tree. Put a reset on a stable ancestor when a counter should run through a whole region, or on the heading that defines a new nested scope. A reset on a chapter heading is useful when its section counter should begin again for each chapter.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
#1 Best Overall
Sale
HTML and CSS: Design and Build Websites
  • HTML CSS Design and Build Web Sites
  • Comes with secure packaging
  • It can be a gift option

The element must generate a box to participate. An element with display: none does not set, reset, or increment counters. If a heading is hidden that way, it will not advance the numbering even though it remains in the HTML.

Number chapters and nested sections

This minimal example numbers chapters as 1, 2, and sections within each chapter as 1.1, 1.2, then 2.1. Save it as an HTML file and use it as the input to wkhtmltopdf:

<!doctype html>
<html>
<head>
  <meta charset="utf-8">
  <style>
    body {
      counter-reset: chapter;
    }

    h1 {
      counter-increment: chapter;
      counter-reset: section;
    }

    h1::before {
      content: "Chapter " counter(chapter) ". ";
    }

    h2 {
      counter-increment: section;
    }

    h2::before {
      content: counter(chapter) "." counter(section) " ";
    }
  </style>
</head>
<body>
  <h1>First chapter</h1>
  <h2>First section</h2>
  <h2>Second section</h2>
  <h1>Second chapter</h1>
  <h2>First section</h2>
</body>
</html>

The body starts the chapter counter. Each h1 increments it and resets the section counter for its own chapter. Each h2 increments the section counter. The pseudo-elements then display the values before the heading text.

Adding a third level

To number subsections, add another counter and reset it at each section heading. The order is important: the h2 increments the section counter and resets the subsection counter; an h3 increments that third counter.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
h2 {
  counter-increment: section;
  counter-reset: subsection;
}

h2::before {
  content: counter(chapter) "." counter(section) " ";
}

h3 {
  counter-increment: subsection;
}

h3::before {
  content: counter(chapter) "." counter(section) "." counter(subsection) " ";
}

For deeper or repeating nested structures, counters() can print all nested instances of one counter name. That is useful when the markup represents a genuinely nested list, but separate names such as chapter, section, and subsection are often easier to inspect when numbering heading levels.

Keep the markup and scope predictable

Start with adjacent heading elements as in the example. If your production template wraps each heading in containers, test that exact structure: wkhtmltopdf users have reported wrapper-sensitive numbering, including duplicate values with separately wrapped headings. This is a renderer-specific compatibility report, not a general CSS counter rule. Add wrappers one at a time and inspect the resulting PDF after each change.

Rank #3
Sale
Web Design with HTML, CSS, JavaScript and jQuery Set
  • Brand: Wiley
  • Set of 2 Volumes
  • A handy two-book set that uniquely combines related technologies Highly visual format and accessible language makes these books highly effective learning tools Perfect for beginning web designers and front-end developers

Prefer placing counter-reset on the heading or ancestor that owns the scope, rather than only on a pseudo-element. A pseudo-element can display a counter, but it is a fragile place to establish state needed by following sibling headings.

Print “Page X of Y” in wkhtmltopdf

CSS heading counters do not number physical PDF pages. For the page position in the finished document, wkhtmltopdf documents header and footer substitutions: [page] is the current page and [topage] is the last page.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
wkhtmltopdf 
  --footer-right 'Page [page] of [topage]' 
  input.html output.pdf

Run the command with the same wkhtmltopdf executable and options used in deployment. The library settings also document pageOffset and pagesCount controls for page-number handling. Do not confuse those header/footer substitutions with CSS generated content: the former are wkhtmltopdf’s documented production interface for page numbering.

Why not rely on CSS @page counters?

CSS Paged Media defines page-associated page and pages counters for conforming paged-media user agents. That standard context does not establish that a particular wkhtmltopdf binary supports every page-margin rule or counter as expected. If you want to use @page counters, build a small test PDF with the exact binary, stylesheet, and output settings you ship. For a production “Page X of Y” footer, the documented placeholders are the less speculative choice.

Test the generated PDF, not just browser preview

  1. Save a minimal reproduction. Use only the headings, CSS resets, increments, and generated content needed to show the numbering issue.
  2. Render it with the deployment binary. Browser output is useful for checking the general CSS idea, but it does not prove that wkhtmltopdf will produce the same PDF.
  3. Check the first and last values. Confirm the first chapter and section start where intended, that a new chapter restarts its section count, and that the final page footer shows the expected total.
  4. Add template structure gradually. Introduce wrappers and layout rules one at a time; render again when a counter changes unexpectedly.
  5. Pin the executable used in production. Keep the wkhtmltopdf binary/version and relevant command options consistent between local testing and deployment, and rerun the PDF check when either changes.

This approach separates a counter-scope mistake from behavior tied to the renderer or to the structure of a larger template.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Troubleshoot missing or incorrect numbers

The number does not appear

  • Check that the pseudo-element has a non-empty content declaration, such as content: counter(chapter) ". ";.
  • Check that the counter is reset or incremented before it is read and that the counter name is spelled identically in each rule.
  • Check the rendered PDF for the number; browser preview alone is not the final test for wkhtmltopdf.

Every heading shows the same value

  • Verify that counter-increment is on the heading selector that matches the actual HTML elements.
  • Make sure the heading is not display: none; an element with no generated box does not participate in counter operations.
  • Reduce the page to adjacent headings. If that works but the full template does not, restore wrappers individually and test the production structure.

Sections do not restart for a new chapter

  • Put counter-reset: section; on the h1 that starts each chapter, not just on body. A body-level reset initializes the counter but does not restart it at every chapter.
  • Keep the reset attached to the element whose scope should own the following section headings. Putting it only on h1::before can leave sibling headings outside the intended scope.

Numbers duplicate or change after adding wrappers

Return to the minimal adjacent-heading reproduction, then add the same wrapper hierarchy used by the template one level at a time. A community report describes duplicate numbering with headings in separate div elements while adjacent headings worked. Treat that as a reason to test your markup on your own wkhtmltopdf binary, not as a rule that all wrappers break counters.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

The page footer is blank or wrong

Use wkhtmltopdf’s documented spelling and brackets: [page] and [topage]. Put the text in a header or footer option such as --footer-right, rather than expecting a CSS counter(page) declaration to act as the CLI substitution. Check the output PDF and command options with the executable actually used for deployment.

Or skip the browser setup

If your goal is to capture a URL as an image or PDF rather than to run a wkhtmltopdf conversion, ScreenshotNeo is a separate website screenshot API and MCP server. It does not configure wkhtmltopdf or fix CSS counter behavior. One GET request can return a screenshot or PDF; for a web screenshot, a cURL call looks like this. See the ScreenshotNeo API documentation for request options.

curl -G "https://api.screenshotneo.com/v1/shot" 
  -d access_key=YOUR_API_KEY 
  --data-urlencode url=https://stripe.com 
  -o shot.webp

ScreenshotNeo accepts cookie and consent banners before capture and removes more than 60 known consent platforms, newsletter popups, and chat widgets; these steps can be turned off. Bot checks and CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and responses identify the page verdict and billing status in headers. Its MCP server provides take_screenshot, get_page_info, and capture_pdf tools for AI agents using Claude, Cursor, or another MCP client.

The Free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000 screenshots. Every feature is available on every plan, and yearly billing gives two months free. Sign up for ScreenshotNeo’s free plan to try 1,000 screenshots a month without a card.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Frequently Asked Questions

Should CSS counters be used for a numbered list in the source HTML?

If list semantics matter to assistive technology or downstream document processing, use ordered-list markup for the list itself; generated counter text is visual CSS content rather than text inserted into the HTML.

Can I use wkhtmltopdf placeholders inside a CSS heading counter?

They serve different purposes: CSS counters number document elements, while wkhtmltopdf’s bracketed substitutions provide header/footer page values. Put page placeholders in the corresponding wkhtmltopdf header or footer option.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.