Using CSS Counters

Master automatic numbering in CSS with counter-reset, counter-increment, and counter() functions. Create dynamic numbering systems without JavaScript.

What Are CSS Counters?

CSS counters are one of CSS's most powerful yet underutilized features. They let you automatically number elements in your markup without touching your HTML or JavaScript. Think of them as CSS-native variables that track how many times they've been used, letting you create numbered lists, section headings, table of contents, and more--all through pure CSS. This capability has been part of CSS since the early days and is now supported across all modern browsers, making it a reliable technique for any web project.

At their core, CSS counters are variables that CSS can maintain and increment or decrement based on where elements appear in your document. Unlike HTML's ordered list (<ol>) which has predefined numbering behavior, CSS counters give you complete control over when and how counting happens. You can apply them to any element--headings, paragraphs, list items, or even custom elements--creating automatic numbering systems that adapt to your content structure. The browser handles all the counting logic, meaning no JavaScript is required and the numbering updates automatically as content changes.

The magic of CSS counters lies in their flexibility. Traditional numbered lists give you decimal, alphabetic, or Roman numbering with limited customization. CSS counters let you start at any number, increment by any value (positive or negative), use custom numbering styles, and nest counters for hierarchical numbering. This makes counters ideal for documentation sites, legal documents, academic papers, or any content where you need structured, automatic numbering.

For teams building modern web applications, CSS counters provide an elegant way to add professional polish to content-heavy sites without adding JavaScript dependencies.

How Counters Work

CSS counters follow a simple three-step process: first, you define and initialize a counter using counter-reset; second, you increment (or decrement) the counter as elements appear using counter-increment; third, you display the counter value using the counter() or counters() function within a pseudo-element's content property. This separation of concerns gives you precise control over when and where counting occurs.

The three-step flow in action: The counter-reset property establishes the counter and sets its initial value on a parent element. As each target element is encountered during rendering, counter-increment changes the counter's value. Finally, the counter() function reads the current value and outputs it through the content property of a pseudo-element like ::before. Understanding these three operations enables you to create sophisticated numbering systems that would otherwise require JavaScript or server-side processing.

Basic CSS Counter Setup
1article {2 /* Step 1: Define and initialize the counter */3 counter-reset: section;4}5 6section {7 /* Step 2: Increment the counter for each section */8 counter-increment: section;9}10 11section::before {12 /* Step 3: Display the counter value */13 content: "Section " counter(section) ": ";14}

Core Counter Properties

The CSS counter system comprises three properties that work together to create automatic numbering.

counter-reset

The counter-reset property creates a new counter and optionally sets its initial value. By default, a counter starts at zero, but you can specify any starting point. The syntax allows you to reset multiple counters at once--for example, counter-reset: chapter 0 section 0 figure 1; would initialize three counters with different starting values in a single declaration.

Where you apply counter-reset matters significantly. The counter resets at the element where you declare it, and all descendants can access and modify that counter. If you apply counter-reset to a heading instead of its parent, each heading would reset the counter independently, breaking your numbering sequence. Best practice is to apply counter-reset to a common ancestor that wraps all elements you want to number.

counter-increment

The counter-increment property specifies how much to change the counter's value when an element appears. By default, incrementing adds 1 to the counter, but you can specify any integer--positive to count up, negative to count down. This flexibility lets you create non-sequential numbering, skip numbers intentionally, or create countdown sequences. You can also use counter-increment without specifying an integer, which defaults to 1 for simple sequential numbering. Importantly, counter-increment only affects counters that have been created with counter-reset--trying to increment a non-existent counter has no effect.

counter-set

The counter-set property directly assigns a specific value to a counter, overriding its current value. Unlike counter-increment which changes the value relative to its current state, counter-set provides absolute control. This is useful when you need to jump to a specific number or handle special cases where incremental counting doesn't fit your needs--like starting numbering at 100 for an appendix section or skipping to a specific figure number in technical documentation.

Counter Properties at a Glance

counter-reset

Creates a counter and sets its initial value. Apply to a parent element for proper scoping.

counter-increment

Changes the counter value by a specified amount. Defaults to +1, can be any positive or negative integer.

counter-set

Directly assigns a specific value to a counter, overriding the current state.

counter()

Function to display a counter's value with optional style parameter (decimal, roman, alpha, etc.).

counters()

Function for nested counters with a separator string between hierarchical levels.

Displaying Counters

Once you've set up your counters, you display their values using the counter() or counters() functions within the content property of pseudo-elements.

The counter() Function

The counter() function displays a counter's current value and accepts two parameters: the counter name and an optional counter style. The counter style determines how the number is displayed--decimal (default), upper-alpha, lower-roman, and many others. This works exactly like the list-style-type property, giving you access to predefined counter styles.

The default counter style is decimal, which produces plain numbers (1, 2, 3). Other useful styles include upper-alpha (A, B, C), lower-alpha (a, b, c), upper-roman (I, II, III), and lower-roman (i, ii, iii). You can even use thai for Thai numerals or cjk-decimal for Chinese, Japanese, and Korean number systems, making counters invaluable for internationalized content.

Nested Counting with counters()

When you need hierarchical numbering--like 1.1, 1.2, 2.1, 2.2--the counters() function handles nested counting automatically. It accepts three parameters: the counter name, a separator string, and an optional counter style. The separator appears between nested counter values, creating hierarchical numbering patterns.

The separator string in counters() gives you complete control over how nested numbers appear. Common patterns include periods (1.1.1), arrows (1>1>1), colons (1:1:1), or even custom characters. Choose separators that match your design system or content type--legal documents often use periods, while technical documentation might use arrows for clarity.

Nested Counter Example
1section {2 counter-reset: subsection;3}4 5section h2::before {6 counter-increment: section;7 content: counter(section) ". ";8}9 10section .item {11 counter-reset: subitem;12}13 14section .item::before {15 counter-increment: subitem;16 content: counter(section) "." counter(subitem) ": ";17}

Styling with the ::marker Pseudo-Element

The ::marker pseudo-element provides a dedicated way to style list markers, and it works with CSS counters for custom numbering. Unlike ::before, which can contain any content, ::marker is specifically designed for the marker portion of list items and other elements with counter-like markers.

The ::marker pseudo-element supports several CSS properties specifically for marker styling, including color, font properties, and content for replacing the default marker with custom content. This gives you precise control over the appearance of numbering without affecting the rest of your content. Browser support for ::marker styling has improved significantly, making it a viable option for modern projects.

For broader compatibility, many developers still use ::before with list-style-type: none as a fallback approach. This technique creates a pseudo-element that acts as a custom marker, allowing full CSS control while maintaining consistent behavior across older browsers.

List Styling Options

Beyond custom counters, CSS provides numerous built-in counter styles through the list-style-type property. Unordered lists support disc, circle, and square markers, while ordered lists offer extensive options including decimal, decimal-leading-zero, upper-alpha, lower-alpha, upper-roman, lower-roman, and many more. Modern CSS even allows using arbitrary strings as markers, opening new possibilities for creative numbering designs.

Common Use Cases

CSS counters excel in scenarios where automatic numbering improves content organization without manual maintenance.

Auto-Numbered Headings

Documentation sites, blogs, and articles often need numbered headings for easy reference. CSS counters add this numbering automatically, so you never manually update numbers when adding or reordering sections. This is particularly valuable for long-form content where headings might change during editing.

Table of Contents

Counters can generate dynamic tables of contents that automatically reflect your document structure. Combined with internal links, this creates navigation systems that adapt to your content without manual maintenance. Each TOC entry can show the same numbering as the corresponding headings.

Legal and Technical Documents

Legal documents, specifications, and technical manuals often require numbered sections, clauses, or figures. CSS counters provide a reliable way to implement this numbering without embedding numbers in the content itself. This separation means content editors can add or remove sections without renumbering everything manually.

Step-by-Step Instructions

When presenting procedures or tutorials, numbered steps improve clarity and reference ability. CSS counters can number these steps automatically, even when steps are distributed across different sections or include conditional branches.

.instruction {
 counter-reset: step;
}

.instruction .step {
 counter-increment: step;
}

.instruction .step::before {
 content: "Step " counter(step) ": ";
 font-weight: bold;
 color: #2563eb;
}

Why Use CSS Counters?

100%

Native CSS

0

JavaScript Required

All

Modern Browsers

Free

Maintenance

Performance Benefits

CSS counters offer significant performance advantages over JavaScript-based numbering solutions. Since counters are handled by the browser's rendering engine, they don't require script execution, DOM manipulation, or reflows when content changes. The browser updates counter values during layout, making them essentially free from a performance perspective.

This native implementation means counters work well with server-side rendering and static site generation. Unlike JavaScript solutions that require hydration, CSS counters render correctly from the first paint. For large documents with many numbered elements, this can make a noticeable difference in perceived performance and time-to-interactive. Counters also integrate seamlessly with CSS containment and content-visibility optimizations, contributing to efficient rendering pipelines without requiring additional JavaScript dependencies.

When building performance-optimized websites, using native CSS features like counters helps reduce bundle size and improve core web vitals. This aligns with modern web development best practices that prioritize lightweight, efficient solutions.

Best Practices

Proper Scoping: Always apply counter-reset to a common ancestor that wraps all elements needing the same counter. This ensures consistent counting behavior and prevents unexpected resets. Counter scoping follows the normal CSS inheritance and cascade rules, so understanding where you reset affects what can access and modify each counter.

Semantic HTML: CSS counters are a presentational enhancement, not a replacement for semantic markup. Use ordered lists (<ol>) when the numbering conveys meaning to users and assistive technologies. Use CSS counters for visual numbering where the numbers are helpful but not essential to understanding the content structure.

Accessibility: While CSS counters display visually, screen readers may not announce them consistently. Ensure that numbering is available through proper heading structure, list markup, or ARIA attributes when it's essential for understanding the content. Test your implementations with actual assistive technology to verify the experience for all users.

Frequently Asked Questions

Conclusion

CSS counters provide a powerful, performant way to add automatic numbering to your content. From simple ordered lists to complex hierarchical documentation, the counter system gives you precise control over how elements are numbered--without JavaScript or manual maintenance.

By understanding the three core properties--counter-reset, counter-increment, and counter-set--alongside the display functions counter() and counters(), you can implement sophisticated numbering systems that scale with your content.

The key to effective counter usage is proper scoping, semantic awareness, and accessibility consideration. Apply counters thoughtfully, use them to enhance rather than replace meaningful markup, and test across browsers. When used well, CSS counters create polished, maintainable documents where numbering adapts automatically as content evolves.

Start using CSS counters today to streamline your numbering workflows and create more maintainable web projects. Whether you're building documentation sites, legal documents, or step-by-step tutorials, CSS counters offer a native solution that performs well and requires no JavaScript dependencies. For organizations looking to modernize their web development practices, mastering CSS counters is a small step that delivers significant benefits in content maintainability and site performance.

Ready to Build Better Websites?

Our team of expert developers specializes in modern CSS techniques and performance-optimized web solutions.

Sources

  1. MDN Web Docs: Using CSS Counters - Official documentation covering all counter properties and usage patterns
  2. CSS-Tricks: Styling Counters in CSS - Comprehensive coverage from basic to advanced styling techniques
  3. Samantha Ming: A Guide to CSS counter - Practical step-by-step guide with clear examples