ScreenshotNeo

BlogHTML to image & PDF

How to Use CSS counter-increment and counter-reset with iText

Learn how to number headings and nested content with CSS counter-reset and counter-increment when converting HTML to PDF with iText pdfHTML.

By the ScreenshotNeo team1 October 20267 min read

How to Use CSS counter-increment and counter-reset with iText

Direct answer: iText pdfHTML lists both counter-reset and counter-increment as supported CSS properties. Initialize a named counter, increment it on the elements you want numbered, and render the value with counter() or counters() in generated content. The separate counter-set property is listed as unsupported, so do not assume that support for these two properties covers every modern CSS counter feature. See the official pdfHTML support matrix for the release you use.

What CSS counters do

A CSS counter is a named value maintained while the document is processed. counter-reset creates or reinitializes the value; counter-increment changes it. A counter has no visible output until you use counter(name) or counters(name, separator), usually in a pseudo-element’s content declaration. The general CSS model is documented by MDN’s CSS counter guide.

Property or function Purpose pdfHTML guidance
counter-reset Creates or restarts one or more named counters, optionally at specified values. Listed as supported.
counter-increment Advances or decreases a counter. The default step is 1. Listed as supported.
counter() Outputs the current value of one counter. Use it in generated content.
counters() Outputs nested counter values with a separator. Useful for section numbers such as 2.3.
counter-set Sets a counter without the reset semantics. Listed as unsupported; avoid it.

Minimal heading-numbering pattern

This pattern follows the documented CSS approach for sequential heading numbers. It is a standards example; the iText support statement comes from the feature matrix rather than an end-to-end rendering claim.

A reset establishes a counter scope, increments advance it, and generated content displays the value.
A reset establishes a counter scope, increments advance it, and generated content displays the value.
body {
  counter-reset: section;
}

h2::before {
  counter-increment: section;
  content: "Section " counter(section) ": ";
}

The body starts the counter at zero. Each matching h2 increments it, then generated content displays the resulting value.

Complete Java example with iText pdfHTML

pdfHTML is iText’s HTML/CSS-to-PDF add-on. The official repository demonstrates conversion through HtmlConverter and the html2pdf dependency. Follow the repository’s version-specific setup instructions and pin a version appropriate for your project.

Maven dependency

<dependency>
  <groupId>com.itextpdf.html2pdf</groupId>
  <artifactId>html2pdf</artifactId>
  <version>YOUR_PDFHTML_VERSION</version>
</dependency>

Java source

import com.itextpdf.html2pdf.HtmlConverter;

import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;

public class CssCounterPdf {
    public static void main(String[] args) throws IOException {
        String html = """
            <!doctype html>
            <html>
            <head>
              <meta charset=\"UTF-8\">
              <style>
                @page { size: A4; margin: 20mm; }
                body { font-family: sans-serif; counter-reset: section; }
                h2 {
                  counter-increment: section;
                  counter-reset: subsection;
                  margin-top: 1.4em;
                }
                h2::before {
                  content: counter(section) \". \";
                }
                h3 {
                  counter-increment: subsection;
                }
                h3::before {
                  content: counter(section) \".\" counter(subsection) \" \";
                }
              </style>
            </head>
            <body>
              <h1>Project guide</h1>
              <h2>Installation</h2>
              <h3>Requirements</h3>
              <p>Install the runtime and dependencies.</p>
              <h3>Configuration</h3>
              <p>Set the application options.</p>
              <h2>Operation</h2>
              <h3>Start a job</h3>
              <p>Submit the first job.</p>
            </body>
            </html>
            """;

        Path input = Path.of("counter-example.html");
        Files.writeString(input, html, StandardCharsets.UTF_8);

        try (FileOutputStream output = new FileOutputStream("counter-example.pdf")) {
            HtmlConverter.convertToPdf(input.toFile(), output);
        }
    }
}

For this structure, the intended labels are 1. Installation, 1.1 Requirements, 1.2 Configuration, 2. Operation, and 2.1 Start a job. Treat that output as the design target and validate complex nesting with the exact pdfHTML release in your build.

Reset and increment syntax

Start at a custom value

body {
  counter-reset: section 0;
}

h2 {
  counter-increment: section 1;
}

An omitted integer defaults to zero for counter-reset and one for counter-increment.

Use several counters

body {
  counter-reset: chapter figure;
}

h2 {
  counter-increment: chapter;
}

figure {
  counter-increment: figure;
}

figure::before {
  content: "Figure " counter(figure) ": ";
}

Multiple counter names may be declared in one reset or increment declaration. Keep each counter’s scope and purpose clear.

Decrement or change by more than one

.backmatter {
  counter-increment: section -1;
}

.special {
  counter-increment: section 2;
}

Negative and larger integer steps are part of the CSS model. Confirm unusual sequences in your installed release.

Nested numbering with counters()

ol.chapter {
  counter-reset: item;
  list-style: none;
}

ol.chapter > li {
  counter-increment: item;
}

ol.chapter > li::before {
  content: counters(item, ".") " ";
}

counters() joins nested instances of a counter. It is useful when nested scopes should produce values such as 2.3.1. The available iText references establish property support, but they do not verify every nested-scope edge case; test your actual document.

Choosing counters, lists, or page references

Need Recommended approach Reason
Number headings or custom blocks CSS counters Numbers are generated from document order and can be styled independently.
Represent a real list HTML ol/ul Native list semantics are clearer for assistive technology and structured content. The support matrix also lists list-style properties.
Show the destination page of a heading in a table of contents target-counter or target-counters This is a page-reference feature, separate from sequential counters. iText documents support beginning with pdfHTML 3.0.3; see the support matrix.
Sequential CSS counters and destination page references solve different numbering problems.
Sequential CSS counters and destination page references solve different numbering problems.

Scope, ordering, and edge cases

  • Reset placement matters: resetting on a parent establishes a scope for descendants. Resetting on a heading restarts that counter at each matching heading.
  • Increment placement matters: put the increment on the element that represents one unit. Putting it on a wrapper can count wrappers rather than headings.
  • Generated content is required: resetting and incrementing alone produces no visible number.
  • Selector coverage matters: a counter only changes where the selector matches. Check that your HTML structure matches the CSS selectors.
  • Browser assumptions do not prove PDF behavior: pdfHTML documents feature support, but the supplied references do not guarantee browser-identical rendering for every nesting or pseudo-element case.
  • Version drift is possible: the support matrix is a live knowledge-base page, while the API references found for CssCounterManager and CssConstants are versioned. Verify the exact release installed in your project.
  • Avoid counter-set: the matrix marks it unsupported. Rewrite the design with reset and increment where possible.

Troubleshooting

Symptom Likely cause Fix
No number appears The counter is never referenced in generated content. Add content: counter(name) or content: counters(name, ".") to a matching pseudo-element.
Every heading shows the same value The counter is reset on each heading. Move the reset to a common ancestor such as body or a document container.
Subsections do not restart at 1 The subsection counter is not reset when a new section begins. Put counter-reset: subsection on the section heading or section container, according to the desired scope.
Numbers skip unexpectedly More than one selector increments the same counter, or a wrapper is counted. Search all declarations for counter-increment and keep one deliberate increment per numbered unit.
Nested values are malformed counter() is used where nested output requires counters(), or scopes are not nested as intended. Use counters(name, ".") and simplify the HTML hierarchy before adding more rules.
counter-set has no effect pdfHTML lists that property as unsupported. Use supported reset/increment rules or generate the value in the source HTML.
PDF output differs from a browser CSS engines and pdfHTML can differ, especially in complex generated-content or nested-counter cases. Check the installed pdfHTML version’s matrix, reduce the test case, and validate the resulting PDF in an automated or manual review step.
Compilation fails around HtmlConverter The html2pdf dependency is missing or versions are mixed. Use the dependency and version instructions from the official repository and keep iText modules compatible.

Performance, reliability, and maintenance

  • Counter calculations are lightweight compared with images, fonts, and large HTML trees. Keep selectors narrow and avoid unnecessary generated content.
  • Use a fixed, versioned pdfHTML dependency in production. Recheck the support matrix when upgrading because the matrix is not a substitute for release-specific regression tests.
  • Maintain a small fixture PDF containing top-level sections, nested sections, a reset, a decrement, and a generated label. Compare extracted text or rendered pages after upgrades.
  • For long documents, prefer a consistent hierarchy and one counter per numbering level. This makes failures easier to isolate than a single counter reused across unrelated components.
  • Do not use sequential CSS counters when the required value depends on final PDF pagination. Use the documented target-counter features for destination page references.

Or skip the browser setup

If your workflow also needs screenshots of the source page or rendered documentation, ScreenshotNeo provides a website screenshot API and MCP server. It is separate from iText PDF conversion, but can remove browser automation from capture jobs.

See the ScreenshotNeo API documentation for all options. A one-call capture looks like this:

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

ScreenshotNeo removes cookie banners, newsletter popups, and chat widgets before the shot. Bot checks, blank pages, failed loads, timeouts, and cache hits are not billed, and response headers identify the page verdict and billing status. Its MCP server lets AI agents use take_screenshot, get_page_info, and capture_pdf. The free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 shots.

Create a free ScreenshotNeo account and start with the included 1,000 monthly screenshots.

FAQ

Does iText support both properties?

Yes. The pdfHTML support matrix lists counter-reset and counter-increment as supported.

Does support include counter-set?

No. The same matrix marks counter-set unsupported.

Why do I need counter()?

Counters are internal values. counter() or counters() turns the value into generated content that can appear in the PDF.

Can counters show table-of-contents page numbers?

Sequential counters number elements. Destination page numbers require the separate target-counter or target-counters capability documented by iText.

Is this identical to browser rendering?

The references establish documented pdfHTML property support, not browser-identical behavior for every edge case. Validate complex nested documents against your exact pdfHTML version.

References: iText pdfHTML feature matrix, iText pdfHTML repository, MDN CSS counters.