W3C

CSS Lists and Counters Module Level 3

Editor's Draft 9 December 2011

This version:
http://dev.w3.org/csswg/css3-lists/
Latest version:
http://www.w3.org/TR/css3-lists/
Previous version:
http://www.w3.org/TR/2002/WD-css3-lists-20021107/
Editor:
Tab Atkins Jr., Google Inc.
Previous Editors:
Ian Hickson, ian@hixie.ch
Tantek Çelı̇k, Microsoft Corporation, tantekc@microsoft.com
Contributors:
Simon Montagu, AOL-TW/Netscape, smontagu@netscape.com
Daniel Yacob, yacob@geez.org
Christopher Hoess, choess@stwing.upenn.edu
Daniel Glazman, AOL-TW/Netscape, glazman@netscape.com

Abstract

CSS is a language for describing the rendering of structured documents (such as HTML and XML) on screen, on paper, in speech, etc. This draft contains the features of CSS level 3 relating to list styling. It includes and extends the functionality of CSS level 2 [CSS21], which builds on CSS level 1 [CSS1]. The main extensions compared to level 2 are a pseudo-element representing the list marker, and a method for authors to define their own list-styles.

Status of this document

This is a public copy of the editors' draft. It is provided for discussion only and may change at any moment. Its publication here does not imply endorsement of its contents by W3C. Don't cite this document other than as work in progress.

The (archived) public mailing list www-style@w3.org (see instructions) is preferred for discussion of this specification. When sending e-mail, please put the text “css3-lists” in the subject, preferably like this: “[css3-lists] …summary of comment…

This document was produced by the CSS Working Group (part of the Style Activity).

This document was produced by a group operating under the 5 February 2004 W3C Patent Policy. W3C maintains a public list of any patent disclosures made in connection with the deliverables of the group; that page also includes instructions for disclosing a patent. An individual who has actual knowledge of a patent which the individual believes contains Essential Claim(s) must disclose the information in accordance with section 6 of the W3C Patent Policy.

Table of contents


1. Introduction

The list model in this module differs in some important ways from the list model in CSS2, specifically in its handling of markers. Implementation experience suggested the CSS2 model overloaded the ::before and ::after pseudo-elements with too much behavior, while at the same time introducing new properties when existing properties were sufficient.

Most block-level elements in CSS generate one principal block box. In this module, we discuss two CSS mechanisms that cause an element to have an associated marker: one method associates one principal block box (for the element's content) with a separate marker box (for decoration such as a bullet, image, or number), and the other inserts a marker box into the principal box. Unlike :before and :after content, the marker box cannot affect the position of the principal box, whatever the positioning scheme.

For instance, the following example illustrates how markers may be used to add parentheses around each numbered list item. This HTML application and style sheet:

<style>
li::marker { content: "(" counter(list-item, lower-roman) ")"; }
li { display: list-item; }
</style>
<ol>
	<li>This is the first item.</li>
	<li>This is the second item.</li>
	<li>This is the third item.</li>
</ol>

should produce something like this:

  (i) This is the first item.
 (ii) This is the second item.
(iii) This is the third item.

With descendant selectors and child selectors, it's possible to specify different marker types depending on the depth of embedded lists.

A future release of this module will probably include ways to render tree lists.

2. Declaring a List Item

To declare a list item, the ‘display’property must be set to ‘list-item’ or ‘inline-list-item’ (defined later in this section). This, in addition to generating a ‘::marker’pseudo-element and enabling the properties described below for that element, causes that element to increment the list item counter ‘list-item’: if the element doesn't otherwise have a ‘counter-increment’ declaration, it must act as if ‘counter-increment: list-item’ were specified. (This does not affect the specified or computed values of the counter properties.)

The ‘list-item’ counter is a real counter, and can be directly affected using the ‘counter-increment’ and ‘counter-reset’ properties. It can also be used in the ‘counter()’ and ‘counters()’ functions.

Property: display
New Value: inline-list-item
Initial: same as CSS2.1
Applies to: same as CSS2.1
Inherited: same as CSS2.1
Percentages: same as CSS2.1
Media: same as CSS2.1
Computed value: same as CSS2.1

The ‘inline-list-item’ display value makes the element a list item, which means it can generate a ::marker pseudo-element. The element also affects the predefined ‘list-item’ counter, as specified above. Otherwise, this display value is treated identically to ‘inline’.

This display value is silly, and only necessary because we haven't yet defined ‘display’ to be split into subproperties. When that happens, "being a list item" will just be a property value that can apply to block, inline, and other elements.

3. Marker Content: The ‘list-style-image’ and ‘list-style-type’ properties

Name: list-style-image
Value: <image> | none
Initial none
Applies To: list items
Inherited: yes
Percentages: N/A
Media: visual
Computed Value: specified value

The ‘list-style-type’ property specifies an image that will be used as the list marker. If the value resolves to a valid image, the image must be used as the default contents of the ::marker pseudo-element.

If the value ‘none’ is provided, or the <image> doesn't resolve to a valid image, then the default contents are given by ‘list-style-type’ instead.

The following example sets the marker at the beginning of each list item to be the image "ellipse.png".

LI { list-style-image: url("http://www.example.com/ellipse.png") }
Name: list-style-type
Value: <string> | <counter-style> | none
Initial: disc
Applies To: list items
Inherited: yes
Percentages: N/A
Media: visual
Computed Value: specified value

When the ‘list-style-image’ property is ‘none’ or not a valid image, the ‘list-style-type’ property must be used to construct the default contents of a list item's marker; otherwise, the ‘list-style-type’ property must be ignored and have no effect.

<string>
The ::marker pseudo-element must use the provided string as its default contents.
<counter-style>

The ::marker pseudo-element's default contents must be the value of the ‘list-item’ counter, formatted according to the given counter style. Algorithms for formatting a value according to a counter style are given later in this spec.

This specification defines a method for authors to create their own counter styles, both named and anonymous, which may be used here. Additionally, many useful counter styles are predefined in the sections on Complex Counter Styles and Predefined Counter Styles.

none
The ::marker pseudo-element must have no default contents. This will suppress the creation of a marker unless the ::marker has its contents specified directly through the ‘content’ property.

The following examples illustrate how to set markers to various values:

ul { list-style-type: "★"; }
/* Sets the marker to a "star" character */

p.note { 
	display: list-item; 
	list-style-type: "Note:"; 
	list-style-position: inside;
}
/* Gives note paragraphs a marker consisting of the string "Note:" */

ol { list-style-type: upper-roman; }
/* Sets all ordered lists to use the upper-roman counter-style 
   (defined in this specification) */

ul { list-style-type: symbols('○' '●' as repeating); }
/* Sets all unordered list items to alternate between empty and 
   filled circles for their markers. */

ul { list-style-type: none; }
/* Suppresses the marker entirely, unless list-style-image is specified
   with a valid image, or the marker's contents are set explicitly via
   the 'content' property. */

The <counter-style> type is defined as:

<counter-style> = <identifier> | <symbols-function>

where the <identifier> must be the name of a valid custom counter style defined with the ‘@counter-style’ rule.

4. Marker Position: The ‘list-style-position’ property

Name: list-style-position
Value: inside | outside
Initial: outside
Applies To: list items
Inherited: yes
Percentages: N/A
Media: visual
Computed Value: specified value

This property specifies the position of the ::marker pseudo-element's box in the list item. Values have the following meanings:

inside
The ::marker pseudo-element is a ‘display:inline’ element placed immediately before the ‘::before’ pseudo-element in the list items principle box, after which the element’s content flows. Note that if there is no inline content, this will create a line box, just as content in an inline ‘::before’ pseudo-element would. Also note that all the properties that apply to inline elements apply to the ::marker pseudo-element in this state, and this ::marker box participates in the inline box model in the normal manner.
outside
As ‘inside’, plus the ‘position’ property on the ::marker pseudo-element must computer to ‘marker’. The section on the new ‘marker’ value for ‘position’ explains the consequences of this. Additionally, the base directionality of the ::marker pseudo-element (used as an input to the bidi resolution algorithm) must be taken from the marker's marker positioning reference element.

Note that a marker is only generated if the computed value of the ‘content’ property for the element's ::marker pseudo-element is not ‘none’.

Should I make this always compute to ‘inside’ when the list item is ‘display:inline-list-item’? See the thread at http://lists.w3.org/Archives/Public/www-style/2011Jun/0232.html.

For example:

<style>
	ul.compact { list-style: inside; }
	ul         { list-style: outside; }
</style>
<ul class=compact>
	<li>first "inside" list item comes first</li>
	<li>second "inside" list item comes first</li>
</ul>
<hr>
<ul>
	<li>first "outside" list item comes first</li>
	<li>second "outside" list item comes first</li>
</ul>

The above example may be formatted as:

  * first "inside" list
  item comes first
  * second "inside" list
  item comes second

========================

* first "outside" list
  item comes first
* second "outside" list
  item comes second

5. The ‘list-style’ shorthand property

Name: list-style
Value: <‘list-style-type’> || <‘list-style-position’> || <‘list-style-image’>
Initial: see individual properties
Applies To: list items
Inherited: yes
Percentages: N/A
Media: visual
Computed Value: see individual properties

The ‘list-style’ property is a shorthand notation for setting the three properties ‘list-style-type’, ‘list-style-image’, and ‘list-style-position’ at the same place in the style sheet.

For example:

UL { list-style: upper-roman inside }  /* Any UL */
UL UL { list-style: circle outside } /* Any UL child of a UL */
		

Using a value of ‘none’ in the shorthand is potentially ambiguous, as ‘none’ is a valid value for both ‘list-style-image’ and ‘list-style-type’. To resolve this ambiguity, a value of ‘none’ in the shorthand must be applied to whichever of the two properties aren't otherwise set by the shorthand.

list-style: none disc;
/* Sets the image to "none" and the type to "disc". */

list-style: none url(bullet.png);
/* Sets the image to "url(bullet.png)" and the type to "none". */

list-style: none;
/* Sets both image and type to "none". */

list-style: none disc url(bullet.png);
/* Syntax error */

Although authors may specify ‘list-style’ information directly on list item elements (e.g., LI in HTML), they should do so with care. Consider the following rules:

ol.alpha li { list-style: lower-alpha; }
ul li       { list-style: disc; }
/* These won't work as expected.  If you nest a <ul>
into an <ol class=alpha>, the first rule's specificity 
will make the <ul>'s list items use the lower-alpha style. */

ol.alpha > li { list-style: lower-alpha; }
ul > li       { list-style: disc; }
/* These work as intended. */

ol.alpha { list-style: lower-alpha; }
ul       { list-style: disc; }
/* So do these, since inheritance will transfer the 
'list-style' value to the list items. */

6. Markers: The ::marker pseudo-element

Markers are created by setting an element's ‘display’ property to ‘list-item’ or ‘inline-list-item’. The ::marker pseudo-element is only created if the computed value of the ‘content’ property for the pseudo-element is not ‘none’.

Just like other generated content, markers generate a box when they're created, which can be styled with the full range of properties and values. Markers are placed at the beginning of their superior parent's content, before the ::before pseudo-element (if it exists). ::marker pseudo-elements are inline by default, though certain values for ‘list-style-position’ on the list item can make the marker box positioned, which can have an effect on the computed value of display.

In the following example, the content is centered within a marker box of a fixed width. This document:

<style>
li::marker { 
  content: "(" counter(counter) ")";
  width: 6em;
  text-align: center;
}
li {
  display: list-item;
  counter-increment: counter;
}
</style>
<ol>
	<li>This is the first item.</li>
	<li>This is the second item.</li>
	<li>This is the third item.</li>
</ol>

should render something like this:

  (1)    This is the 
         first item.
  (2)    This is the 
         second item.
  (3)    This is the 
         third item.

In this example, markers are used to number paragraphs that are designated as "notes":

<style>
p { margin-left: 12 em; }
p.note::marker { 
  content: "Note " counter(note-counter) ":";
  text-align: left;
  width: 10em;
}
p.note {
 display: list-item;
 counter-increment: note-counter;
}
</style>
<p>This is the first paragraph in this document.</p>
<p class="note">This is a very short document.</p>
<p>This is the end.</p>
		

It should render something like this:

            This is the first paragraph 
            in this document.

  Note 1:   This is a very short 
            document.
           
            This is the end.

By using the ::marker pseudo-element, a list's markers can be positioned much more flexibly, and even be styled independently from the text of the list item itself:

<style>
p { margin-left: 8em } /* Make space for counters */
li { list-style-type: lower-roman; }
li::marker { margin: 0 3em 0 0; color: blue; }
</style>
<p>This is a long preceding paragraph ...</p>
<ol>
	<li>This is the first item.</li>
	<li>This is the second item.</li>
	<li>This is the third item.</li>
</ol>
<p>This is a long following paragraph ...</p>

The preceding document should render something like this:

        This is a long preceding
        paragraph ...
      
   i.   This is the first item.
  ii.   This is the second item.
 iii.   This is the third item.

        This is a long following
        paragraph ...

Previously the only way to style a marker was through inheritance; one had to put the desired marker styling on the list item, and then revert that on a wrapper element around the list item's actual contents. Non-inherited properties like ‘margin’ couldn't be adjusted at all on the marker.

6.1. Generating the computed value of the ‘content’ property

If a ::marker pseudo-element has its ‘content’ property set to ‘normal’, the computed value of the marker's ‘content’ property must be determined according to the following algorithm:

  1. If the computed value of ‘list-style-image’ is a valid <image>, then the computed value of the ‘content’ property is that image.
  2. Otherwise, if the computed value of ‘list-style-type’ is a <string>, then the computed value of the ‘content’ property is that string.
  3. Otherwise, if the computed value of ‘list-style-type’ is a valid <counter-style>, then the computed value of the ‘content’ property is <counter-prefix> counter(list-item, <counter-style>) <counter-suffix>, where <counter-prefix> and <counter-suffix> are the values of the prefix and suffix descriptors for the counter style.
  4. Otherwise the computed value is ‘none’.

Given the following style sheet:

li { display: list-item; list-style-type: decimal /* initial value */; }
li::marker { content: normal /* initial value */; }

And the following document fragment:

<li>List Item</li>

The computed value of the ‘content’ property on the ::marker pseudo-element of the list item element is:

counter(list-item, decimal) "."

7. Positioning Markers

This section introduces a new positioning scheme, designed to model the way in which "outside" list markers were traditionally positioned in CSS 2.1. Outside list markers now have their positioning defined in terms of this new value.

The new positioning scheme defined in this section can be used on all elements, not just ::marker pseudo-elements. In some situations, such as legal proceedings or official minutes, the precise form that the list marker takes is a vital part of the content. It's not acceptable for the marker to change (from a custom-defined marker to a default bullet or alpha marker) just because the UA is not rendering CSS, or a server error is temporarily preventing the CSS file from being loaded, as the precise form of the marker is used to officially refer to that segment. The only way to guarantee that the marker will be rendered correctly, regardless of whether CSS is applied, is to specify the marker outside of CSS, directly in the document's markup. However, the page author may still want to style the marker in many of the ways that are available to them when using ordinary CSS-generated markers. To accomodate this, the new positioning scheme can be used to position the marker-in-content as if it were an ordinary CSS-generated marker.

7.1. The ‘marker’ value for ‘position

Property: position
New Value: marker
Initial: same as CSS2.1
Applies to: same as CSS2.1
Inherited: same as CSS2.1
Percentages: same as CSS2.1
Media: same as CSS2.1
Computed value: see prose, otherwise same as CSS2.1

The ‘marker’ value for ‘position’ depends on the element it is set on having a list item ancestor. If the specified value of ‘position’ is ‘marker’ and the element does not have a list item ancestor, ‘position’ must compute to ‘relative’ on the element. An element with position:marker counts as absolutely positioned.

To calculate the marker's position, we must first define a few terms:

ancestor list item
The ancestor list item is the marker's nearest list item ancestor element.
marker positioning reference element

If the ancestor list item has ‘marker-attachment:list-item’, the marker positioning reference element is the ancestor list item.

Otherwise, if the ancestor list item has ‘marker-attachment:list-container’ and has a parent element, the marker positioning reference element is the ancestor list item's parent.

Otherwise, the marker positioning reference element is the ancestor list item.

list item positioning edge
The border edge of the ancestor list item corresponding to the "start" or "before" edge of the marker positioning reference element, whichever is in the ancestor list item's inline axis.
marker positioning edge
The opposing edge relative to the list item positioning edge on the marker's margin box. For example, if the list item positioning edge ended up being the left border edge of the ancestor list item, the marker positioning edge would be the right margin edge of the marker.

The marker's position in the ancestor list item's block axis is calculated according to the normal flow. In the Positioned Layout Module this will be defined more precisely in terms of placeholders.

The marker's position in the ancestor list item's inline axis must be set such that the marker positioning edge is flush with the list item positioning edge.

The purpose of this somewhat convoluted definition is to position the marker flush against its list item, and then when "marker-attachment:list-container", keep all the markers for a given list on the same side of their list items even in mixed-direction text, so that authors can specify padding on only one side and still ensure their markers are visible. And on top of all that, do something sane in the face of potentially differing writing-modes on the marker, list item, and container.

All elements or pseudo-elements with "position:marker" that share a common ancestor list item are known as markers associated with that list item.

The ‘top’, ‘right’, ‘bottom’, and ‘left’ properties specify offsets relative to the top, right, bottom, and left edges (respectively) of the element itself, similar to how relative positioning works.

position:marker’ can be used when the precise list marker is important for the content, not a stylistic choice, but the normal appearance of lists is still desired. For example, this trimmed snippet of the US Code of Laws may be marked up as the following in HTML:

<style>
ol { list-style: none; }
.marker { position: marker; }
</style>
<ol>
   <li>
      <span class='marker'>(a)</span> Definitions.— For purposes of this section—
      <ol>
         <li><span class='marker'>(1)</span> the term “agency” means agency as...</li>
         <li><span class='marker'>(2)</span> the term “individual” means a citizen...</li>
      </ol>
   </li>
   <li>
      <span class='marker'>(b)</span> Conditions of Disclosure.— No agency shall disclose...
      <ol>
         <li><span class='marker'>(1)</span> to those officers and employees of the agency which...</li>
         <li><span class='marker'>(2)</span> required under section 552 of this title;</li>
      </ol>
   </li>
</ol>

The preceding document should render something like this:

 (a) Definitions.— For purposes of this section—
     (1) the term “agency” means agency as...
     (2) the term “individual” means a citizen...
 (b) Conditions of Disclosure.— No agency shall disclose...
     (1) to those officers and employees of the agency which...
     (2) required under section 552 of this title;

Importantly, it will always be presented something like that, with those exact list markers, even if the stylesheet is unavailable, so other documents can refer to those list markers and be confident that the reference will always be resolvable.

7.2. The ‘marker-attachment’ property

By default, elements or ::marker pseudo-elements with ‘position:marker’ position themselves according to their list item's directionality. However, if the list item is grouped with several other list items which may have different directionality (for example, multiple <li>s with different "dir" attributes in an <ol> in HTML), it is sometimes more useful to have all the markers line up on one side, so the author can specify a single "gutter" on that side and be assured that all the markers will lie in that gutter and be visible. The ‘marker-attachment’ property defined in this section allows an author to control this, switching list items to positioning their markers based off the list container's directionality instead.

Property: marker-attachment
Value: list-item | list-container
Initial: list-item
Applies to: list items
Inherited: yes
Percentages: N/A
Media: visual
Computed Value: specified value

When a list item has ‘marker-attachment:list-item’, any markers associated with the list item base their positioning off of the directionality of the list item. When a list item has ‘marker-attachment:list-container’, the associated markers instead base their positioning off of the directionality of the list item's parent element. The normative meaning of this is specified in the section defining position:marker.

Here is a visual rendering of the effect that ‘marker-attachment’ can have on a list. Both of the following renderings are generated from the following HTML, with the only difference being the value of ‘marker-attachment’ on the list:

<ul>
	<li>english one
	<li dir=rtl>OWT WERBEH
	<li>english three
	<li dir=rtl>RUOF WERBEH
</ul>
list-item list-container
  • english one
  • OWT WERBEH
  • english three
  • RUOF WERBEH
  • english one
  • OWT WERBEH
  • english three
  • RUOF WERBEH

8. Automatic Counters and Numbering: the ‘counter-increment’, ‘counter-set’ and ‘counter-reset’ properties

Automatic numbering in CSS2 is controlled with three properties: ‘counter-increment’, ‘counter-set’ and ‘counter-reset’. The counters defined by these properties are used with the ‘counter()’ and ‘counters()’ functions.

Name: counter-increment
Value: [ <identifier> <integer>? ]+ | none
Initial: none
Applies To: all elements
Inherited: no
Percentages: N/A
Media: all
Computed value: specified value
Name: counter-set
Value: [ <identifier> <integer>? ]+ | none
Initial: none
Applies To: all elements
Inherited: no
Percentages: N/A
Media: all
Computed value: specified value
Name: counter-reset
Value: [ <identifier> <integer>? ]+ | none
Initial: none
Applies To: all elements
Inherited: no
Percentages: N/A
Media: all
Computed value: specified value

The ‘counter-increment’ property accepts one or more names of counters (identifiers), each one optionally followed by an integer. The integer indicates by how much the counter is incremented for the element it's set on. The default increment is ‘1’.

The ‘counter-set’ and ‘counter-reset’ properties also contains a list of one or more names of counters, each one optionally followed by an integer. The integer gives the value that the counter is set to on the element it's set on. The default is ‘0’.

counter-reset’ always establishes a new counter scope on the element it's set on. ‘counter-increment’ and ‘counter-set’ sometimes establish a new counter scope on the element they're set on.

This example shows a way to number chapters and sections with "Chapter 1", "1.1", "1.2", etc.

H1:before {
    content: "Chapter " counter(chapter) ". ";
    counter-increment: chapter;  /* Add 1 to chapter */
    counter-reset: section;      /* Set section to 0 */
}
H2:before {
    content: counter(chapter) "." counter(section) " ";
    counter-increment: section;
}

Resetting a counter is done before setting a counter, which is done before incrementing a counter, which is done before using a counter (for example, in the ‘content’ property).

The counter properties follow the cascading rules as normal. Thus, due to cascading, the following style sheet:

H1 { counter-reset: section -1 }
H1 { counter-reset: imagenum 99 }

will only reset ‘imagenum’. To reset both counters, they have to be specified together:

H1 { counter-reset: section -1 imagenum 99 }

The same principles apply to the ‘counter-set’ and ‘counter-increment’ properties.

8.1. Nested counters and scope

Counters are "self-nesting", in the sense that resetting a counter in a child element automatically creates a new instance of the counter. This is important for situations like lists in HTML, where lists can be nested inside lists to arbitrary depth. It would be impossible to define uniquely named counters for each level.

Thus, the following suffices to number nested list items. The result is very similar to that of setting ‘display:list-item’ and ‘list-style: inside’ on the LI element:

OL { counter-reset: item }
LI { display: block }
LI:before { content: counter(item) ". "; counter-increment: item }

The self-nesting is based on the principle that every element or pseudo-element that has a ‘counter-reset’ for a counter X, creates a fresh counter X, the scope of which is the element or pseudo-element, its following siblings up to but not including the first one that resets the same counter, and all the descendants of those elements.

Additionally, if an element has a ‘counter-set’ or ‘counter-increment’ for a counter that is not in scope for the element, it establishes a scope for that counter identically to if ‘counter-reset’ were used on the element to reset the counter to ‘0’.

In the example above, an OL will create a counter, and all children of the OL will refer to that counter.

If we denote by item[n] the nth instance of the "item" counter, and by "(" and ")"the beginning and end of a scope, then the following HTML fragment will use the indicated counters. (We assume the style sheet as given in the example above).

<OL>               <!-- (set item[0] to 0         -->
  <LI>item         <!--  increment item[0] to 1   -->
  <LI>item         <!--  increment item[0] to 2   -->
    <OL>           <!--  (set item[1] to 0        -->
      <LI>item     <!--   increment item[1] to 1  -->
      <LI>item     <!--   increment item[1] to 2  -->
      <LI>item     <!--   increment item[1] to 3  -->
        <OL>       <!--   (set item[2] to 0       -->
          <LI>item <!--    increment item[2] to 1 -->
        </OL>      <!--   )                       -->
        <OL>       <!--   (set item[3] to 0       -->
          <LI>     <!--    increment item[3] to 1 -->
        </OL>      <!--   )                       -->
      <LI>item     <!--  increment item[0] to 3   -->
  <LI>item         <!--  increment item[0] to 4   -->
</OL>              <!-- )                         -->
<OL>               <!-- (set item[4] to 0         -->
  <LI>item         <!--  increment item[4] to 1   -->
  <LI>item         <!--  increment item[4] to 2   -->
</OL>              <!-- )                         -->

As explained in a later section, the ‘counter()’ function generates a string based off of the innermost scope of the named counter, while the ‘counters()’ function generates a string based on the values of all the in-scope counters with a given name.

The following style sheet numbers nested list items as "1", "1.1", "1.1.1", etc.

OL { counter-reset: item }
LI { display: block }
LI:before { content: counters(item, "."); counter-increment: item }

8.2. Counters in elements that do not generate boxes

An element that does not generate a box (for example, an element with ‘display’ set to ‘none’, or a pseudo-element with ‘content’ set to ‘none’) cannot set, reset, or increment a counter. The counter properties are still valid on such an element, they simply must have no effect.

For example, with the following style sheet, H2s with class "secret" do not increment ‘count2’.

h2 { counter-increment: count2; }
h2.secret { display: none; }

Other methods of "hiding" elements, such as setting ‘visibility’ to ‘hidden’, still cause the element to generate a box, and so do not apply here.

9. Printing Counters: the ‘counter()’ and ‘counters()’ functions

Counters have no visible effect by themselves, but their values can be used in the ‘counter()’ and ‘counters()’ functions to generate counter representations. This happens automatically in the default contents of ‘::marker’ pseudo-elements, but can be used by an author anywhere that accepts a string. Their syntax is:

<counter-function>  =  counter(name [, [ <counter-style> | none ] ]? )
<counters-function> = counters(name, <string>[, [ <counter-style> | none ] ]? )

For both functions, if the ‘<counter-style>’ is omitted it defaults to ‘decimal’.

A ‘<counter-function>’ represents the string obtained when one generates a counter representation of the named counter's value using the function's ‘<counter-style>’. If there are multiple counters of that name in scope, the one whose scoping element is closest in document-order to the element on which ‘counter()’ appears is used. If there are no counters of that name in scope, use the value ‘0’ to generate the counter representation. If ‘none’ is provided as the second argument, the ‘<counter-function>’ instead represents the empty string.

H1:before        { content: counter(chno, upper-latin) ". " }
/* Generates headings like "A. A History of Discontent" */

H2:before        { content: counter(section, upper-roman) " - " }
/* Generates headings like "II - The Discontent Part" */

BLOCKQUOTE:after { content: " [" counter(bq, decimal) "]" }
/* Generates blockquotes that end like "... [3]" */

DIV.note:before  { content: counter(notecntr, disc) " " }
/* Simply generates a bullet before every div.note */

P:before         { content: counter(p, none) } 
/* inserts nothing */

A ‘<counters-function>’ represents the string obtained by the following algorithm:

  1. Collect all counters of the given name that are in scope for the elements on which ‘counters()’ appears, sorted by document-order of each scope's scoping element. If there are no such counters, run the rest of this algorithm as if there were a single counter with the value ‘0’.
  2. Render each counter as a string by generating a counter representation of the counter's value using the function's <counter-style>. If ‘none’ is provided as the third argument, instead render each counter as the empty string.
  3. Concatenate all of the counter representations in order, placing a copy of the string provided as the second argument between each adjacent pair, and return the resulting string.

The following example shows a simple use of the ‘counters()’ function:

<ul>
  <li>one</li>
  <li>two
    <ul>
      <li>nested one</li>
      <li>nested two</li>
    </ul>
  </li>
  <li>three</li>
</ul>
<style>
ul { counter-reset: list-item; }
li { counter-increment: list-item; }
li::marker { content: '(' counters(list-item,'.') ') '; }
</style>

The preceding document should render something like this:

(1) one
(2) two
   (2.1) nested one
   (2.2) nested two
(3) three

Because counter scopes extend to siblings as well, they can be used to number headings and subheadings, which aren't nested within each other. Unfortunately, this precludes the use of ‘counters()’ as the scopes of siblings won't nest, but one can create multiple counters and manually reference them instead:

<h1>First H1</h1>
...
<h2>First H2 in H1</h2>
...
<h2>Second H2 in H1</h2>
...
<h3>First H3 in H2</h3>
...
<h1>Second H1</h1>
...
<h2>First H2 in H1</h2>
...
<style>
body { counter-reset: h1 h2 h3; }
h1   { counter-increment: h1; counter-reset: h2 h3;}
h2   { counter-increment: h2; counter-reset:    h3; }
h3   { counter-increment: h3; }
h1::before { content: counter(h1,upper-alpha) ' '; }
h2::before { content: counter(h1,upper-alpha) '.' 
                      counter(h2,decimal) ' '; }
h3::before { content: counter(h1,upper-alpha) '.' 
                      counter(h2,decimal) '.' 
                      counter(h3,lower-roman) ' '; }
</style>

The preceding document should render something like this:

A First H1
...
A.1 First H2 in H1
...
A.2 Second H2 in H1 
...
A.2.i First H3 in H2
...
B Second H1
...
B.1 First H2 in H1
...

10. Defining Custom Counter Styles: the ‘@counter-style’ rule

CSS 2.1 defined a handful of useful counter styles based on the styles that HTML traditionally allowed on ordered and unordered lists. This tiny set, though, is quite inadequate for modern web pages; displaying an ordered list with markers based on the latin alphabet while the content is Arabic seems quite incongruous!

Unfortunately, the set of potentially useful list styles is too large to specify ahead of time - the world contains thousands of languages and hundreds of scripts, not to mention the near-infinite stylistic variations found on the web that go beyond mere language-based variation. The ‘@counter-style’ rule allows CSS to address this in an open-ended manner, by allowing the author to define their own counter styles. These styles can then be used in the ‘list-style-type’ property or in the ‘counter()’ and ‘counters()’ functions.

A counter style defines how to construct the representation of a counter value. Counter styles are composed of:

The algorithm is usually specified implicitly by a combination of the ‘type’, ‘symbols’, and ‘additive-symbols’ properties, but some counter styles instead have their algorithm explicitly defined in the Complex Counter Styles section.

When asked to generate a counter representation using a particular counter style for a particular counter value, follow these steps:

  1. If the counter value is outside the range of the counter style, exit this algorithm and instead generate a counter representation using the counter style's fallback style and the same counter value.
  2. Using the counter value and the counter algorithm for the counter style, generate an initial representation for the counter value.
  3. Return the representation.

Note that the prefix and suffix don't play a part in this algorithm. This is intentional; the prefix and suffix aren't part of the string returned by the counter() or counters() functions. Instead, the prefix and suffix are added by the algorithm that constructs the computed value of the ‘contents’ property for the ::marker pseudo-element. This also implies that the prefix and suffix always come from the specified counter-style, even if the actual representation is constructed by a fallback style.

The general form of an ‘@counter-style’ rule is:

@counter-style <counter-style-name> {
	[ descriptor: value; ]+
}
	

Each @counter-style rule specifies a value for every counter-style descriptor, either implicitly or explicitly. Those not given explicit value in the rule take the initial value listed with each descriptor in this specification. These descriptors apply solely within the context of the @counter-style rule in which they are defined, and do not apply to document language elements. There is no notion of which elements the descriptors apply to or whether the values are inherited by child elements. When a given descriptor occurs multiple times in a given @counter-style rule, only the last specified value is used; all prior values for that descriptor must be ignored. Unknown descriptors must be ignored; they must not make the ‘@counter-style’ rule invalid.

Defining a ‘@counter-style’ must make it available to the entire document in which it is included. If multiple ‘@counter-style’ rules are defined with the same name, standard cascade rules must be used to determine which declaration "wins": first, they are compared by origin, with author rules beating user rules, and user rules beating UA rules; second, they are compared by document order, with later rules beating earlier rules. ‘@counter-style’ rules cascade "atomically" - if one replaces another of the same name, it replaces it entirely, rather than just replacing the specific descriptors it specifies.

This at-rule conforms with the forward-compatible parsing requirement of CSS; parsers may ignore these rules without error. Any descriptors that are not recognized or implemented by a given user agent must be ignored. The <counter-style-name> must be be a valid identifier and must not be "decimal", "default", "inherit", "initial", "inside", "none", or "outside"; otherwise the @counter-style is invalid and must be ignored.

10.1. Counter algorithms: the ‘type’ descriptor

Name: type
Value: repeating | numeric | alphabetic | symbolic | additive | [non-repeating <integer>?] | [ override <counter-style-name> ]
Initial: symbolic

The ‘type’ descriptor specifies which algorithm will be used to construct the counter's representation based on the counter value. For example, ‘repeating’ type counter styles just cycle through their symbols repeatedly, while numeric type counter styles interpret their symbols as digits and build their representation accordingly. The types are defined as follows:

10.1.1. repeating

If the type is repeating, the ‘symbols’ descriptor must contain at least one counter symbol. This type is defined over all counter values.

The repeating counter type cycles repeatedly through its provided symbols, looping back to the beginning when it reaches the end of the list, similar to the default disc counter style. It can be used for simple bullets (just provide a single counter symbol), or for cycling through multiple bullets. The first counter symbol is used as the representation of the value 1, the second counter symbol (if it exists) is used as the representation of the value 2, etc.

In general, if there are length counter symbols and a representation is being constructed for the value value, the representation is the counter symbol at index ( (value-1) mod length) of the list of counter symbols (0-indexed).

A "triangle bullet" counter style can be defined as:

@counter-style triangle {
	type: repeating;
	symbols: '▶';
	suffix: '';
}
		

It will then produce lists that look like:

▶  One
▶  Two
▶  Three
		

10.1.2. numeric

If the type is numeric, the ‘symbols’ descriptor must contain at least two counter symbols. This type is defined over all counter values.

The numeric counter type cycles interprets the list of counter symbols as digits to a number system, similar to the default decimal counter style. The first counter symbol in the list is interpreted as the digit 0, the second as the digit 1, and so on. If there are length counter symbols, the representation is a base length number using the counter symbols as digits.

To construct the representation, run the following algorithm. Let length be the length of the list of counter symbols, value initially be the counter value, S initially be the empty string, negative be a boolean flag that is initially false, and symbol(n) be the nth counter symbol in the list of counter symbols (0-indexed).

  1. If value is 0, append symbol(0) to S and return S.
  2. If value is negative, run the next step of this algorithm with value being the absolute value of value instead.
  3. While value is not equal to 0:
    1. Prepend symbol( value mod length ) to S.
    2. Set value to floor( value / length ).
  4. If value was originally negative, wrap S in the counter style's negative sign, as specified in the section for the negative descriptor.
  5. Return S.

A "trinary" counter style can be defined as:

@counter-style trinary {
	type: numeric;
	symbols: '0' '1' '2';
}
		

It will then produce lists that look like:

1.   One
2.   Two
10.  Three
11.  Four
12.  Five
20.  Six
		

10.1.3. alphabetic

If the type is alphabetic, the ‘symbols’ descriptor must contain at least two counter symbols. This type is defined only over strictly positive counter values.

The alphabetic counter type interprets the list of counter symbols as digits to an alphabetic numbering system, similar to the default ‘lower-alpha’ counter style. Alphabetic numbering systems are commonly used for lists, and also appear in many spreadsheet programs to number columns. The first counter symbol in the list is interpreted as the digit 1, the second as the digit 2, and so on. If there are length counter symbols, the representation is a base length alphabetic number using the counter symbols as digits. Alphabetic numbering systems do not contain a digit representing 0.

To construct the representation, run the following algorithm. Let length be the length of the list of counter symbols, value initially be the counter value, S initially be the empty string, and symbol(n) be the nth counter symbol in the list of counter symbols (0-indexed).

While value is not equal to 0:

  1. Set value to value - 1.
  2. Prepend symbol( value mod length ) to S.
  3. Set value to floor( value / length ).

Finally, return S.

A counter style using go stones can be defined as:

@counter-style go {
	type: alphabetic;
	symbols: url(white.svg) url(black.svg);
	suffix: '';
}
		

It will then produce lists that look like:

One
Two
Three
Four
Five
Six
Seven

This example requires support for SVG images to display correctly.

Alphabetic styles may also be used to simulate a fixed-width numeric style:

@counter-style fixed-decimal {
	type: alphabetic;
	symbols: '0' '1' '2' '3' '4' '5' '6' '7' '8' '9';
}

ol { 
	list-style: fixed-decimal;
	counter-reset: list-item 1111; 
}
		

This will produce lists that look like:

0001.  One
0002.  Two
0003.  Three
0004.  Four
0005.  Five
0006.  Six
		

Two-digit numbers start at value 11, three-digit numbers start at value 111, etc..

Should I instead explicitly provide a fixed-width numeric counter type? I'd like to see if this sort of numbering is used in the wild first.

10.1.4. symbolic

If the type is symbolic, the ‘symbols’ descriptor must contain at least one counter symbol. This type is defined only over strictly positive counter values.

The symbolic counter type cycles repeatedly through its provided symbols, doubling, tripling, etc. the symbols on each successive pass through the list. For example, if the original symbols were "*" and "†", then on the second pass they would instead be "**" and "††", while on the third they would be "***" and "†††", etc. It can be used for footnote-style markers, and is also sometimes used for alphabetic-style lists for a slightly different presentation than what the alphabetic type presents.

To construct the representation, run the following algorithm. Let length be the length of the list of counter symbols, value initially be the counter value, S initially be the empty string, and symbol(n) be the nth counter symbol in the list of counter symbols (0-indexed).

  1. Let the chosen symbol be symbol(value mod length).
  2. Let the representation length be floor( (value - 1) / length ).
  3. Append the chosen symbol to S a number of times equal to the representation length.

Finally, return S.

An "footnote" counter style can be defined as:

@counter-style footnote {
	type: symbolic;
	symbols: '*' '⁑' '†' '‡';
	suffix: '';
}
		

It will then produce lists that look like:

*.   One
⁑.   Two
†.   Three
‡.   Four
**.  Five
⁑⁑.  Six
		

Some style guides mandate a list numbering that looks similar to ‘upper-alpha’, but repeats differently after the first 26 values, instead going "AA", "BB", "CC", etc. This can be achieved with the symbolic type:

@counter-style upper-alpha-legal {
	type: symbolic;
	symbols: 'A' 'B' 'C' 'D' 'E' 'F' 'G' 'H' 'I' 'J' 'K' 'L' 'M' 
	        'N' 'O' 'P' 'Q' 'R' 'S' 'T' 'U' 'V' 'W' 'X' 'Y' 'Z';
}

This style is identical to ‘upper-alpha’ through the first 27 values, but they diverge after that, with ‘upper-alpha’ going "AB", "AC", "AD", etc. Starting at the 53rd value, ‘upper-alpha’ goes "BA", "BB", "BC", etc., while this style jumps up to the triple digits with "AAA", "BBB", "CCC", etc.

The symbolic type will produce representations with sizes that are linear in the magnitude of the counter value. This can potentially be abused to generate excessively large representations and consume undue amounts of the user's memory or even hang their browser. User agents must support representations at least 20 characters long, but they may choose to instead use the fallback style for representations that would be longer than 20 characters.

10.1.5. non-repeating

If the type is non-repeating, the ‘symbols’ descriptor must contain at least one counter symbol. This type is defined over counter values in a finite range, starting with the first symbol value and having a length equal to the length of the list of counter symbols.

The non-repeating counter type is for representing counter styles that only have a finite number of representations. For example, Unicode defines several limited-length runs of special characters meant for lists, such as circled digits.

When this type is specified, it may optionally have an integer provided after it, which sets the first symbol value. If it is omitted, the first symbol value is 1.

The first counter symbol is the representation for the first symbol value, and subsequent counter values are represented by subsequent counter symbols. Once the list of counter symbols is exhausted, further values cannot be represented by this type, and must instead be represented by the fallback counter style.

A "box-corner" counter style can be defined as:

@counter-style box-corner {
	type: non-repeating;
	symbols: '◰' '◳' '◲' '◱';
	suffix: ':';
}
		

It will then produce lists that look like:

◰:  One
◳:  Two
◲:  Three
◱:  Four
5:  Five
6:  Six
		

10.1.6. additive

If the type is additive, the ‘additive-symbols’ descriptor must contain at least one additive tuple. This type is nominally defined over all counter values (see algorithm, below, for exact details)

The additive counter type takes as many of the largest symbols that it can, then as many of the next largest symbol, etc. until the sum of all the symbols equals the counter value. It can be used to implement roman numerals, and additionally is used to represent the numbering system of several languages which use different characters for the digits in differnt positions.

To construct the representation, run this algorithm. Let value initially be the counter value, S initially be the empty string, and symbol list initially be the list of additive tuples.

  1. If value is initially 0, and there is an additive tuple with a weight of 0, append that tuple's counter symbol to S and return S.
  2. If value is negative, run the next step of this algorithm with value being the absolute value of value instead.
  3. While value is greater than 0 and there are elements left in the symbol list:
    1. Pop the first additive tuple from the symbol list. This is the current tuple.
    2. Append the current tuples counter symbol to S floor( value / current tuple’s weight ) times (this may be 0).
    3. Decrement value by the current tuple's weight multiplied by the number of times the current tuple was appended to S in the previous step.
  4. If value was originally negative, wrap S in the counter style's negative sign, as specified in the section for the negative descriptor.
  5. If the loop ended because value is 0, return S. Otherwise, the given counter value cannot be represented by this counter style, and must instead be represented by the fallback counter style.

A "dice" counter style can be defined as:

@counter-style dice {
	type: additive;
	additive-symbols: 6 '⚅', 5 '⚄', 4 '⚃', 3 '⚂', 2 '⚁', 1 '⚀';
	suffix: '';
}
		

It will then produce lists that look like:

⚀    One
⚁    Two
⚂    Three
...
⚅⚄   Eleven
⚅⚅   Twelve
⚅⚅⚀  Thirteen
		

The additive type will produce representations with sizes that are linear in the magnitude of the counter value. This can potentially be abused to generate excessively large representations and consume undue amounts of the user's memory or even hang their browser. User agents must support representations at least 20 characters long, but they may choose to instead use the fallback style for representations that would be longer than 20 characters.

10.1.7. override

The override type allows an author to use the algorithm of another counter style, but alter other aspects, such as the negative sign or the suffix. If a counter style uses the override type, any unspecified descriptors must be taken from the specified counter style, rather than taking their initial values.

If a @counter-style uses the override type, it must not contain a ‘symbols’ or ‘additive-symbols’ descriptor; otherwise it is invalid and must be ignored. If the specified counter style name isn't the name of any currently-defined counter style, it must be treated as if it was overriding the decimal counter style.

10.2. Formatting negative values: the ‘negative’ descriptor

Name: negative
Value: <string> <string>?
Initial: "\2D" ("-" hyphen-minus)

The ‘negative’ descriptor defines how to alter the representation when the counter value is negative. Not all counter types can render negative numbers.

The first string in the value is prepended to the representation when the counter value is negative. The second string, if specified, is appended to the representation when the counter value is negative.

For example, specifying ‘negative: "(" ")";’ will make negative values be wrapped in parentheses, which is sometimes used in financial contexts, like "(2) (1) 0 1 2 3...".

10.3. Symbols before the marker: the ‘prefix’ descriptor

Name: prefix
Value: <string>
Initial: "" (the empty string)

The ‘prefix’ descripter specifies a string that is prepended to the marker representation. Prefixes are only added by the algorithm for constructing the default contents of the ::marker pseudo-element; the prefix is not added automatically when the counter() or counters() functions are used. Prefixes are added to the representation after negative signs.

10.4. Symbols after the marker: the ‘suffix’ descriptor

Name: suffix
Value: <string>
Initial: "\2E" ("." full stop)

The ‘suffix’ descripter specifies a string that is appended to the marker representation. Suffixes are only added by the algorithm for constructing the default contents of the ::marker pseudo-element; the suffix is not added automatically when the counter() or counters() functions are used. Suffixes are added to the representation after negative signs.

10.5. Limiting the counter scope: the ‘range’ descriptor

Name: range
Value: [ [ <integer> | infinite ]{2} ]# | auto
Initial: auto

The ‘range’ descriptor defines the ranges over which the counter style is defined. If a counter style is used to represent a counter value outside of its ranges, the counter style instead drops down to its fallback counter style.

If the value is not ‘auto’, the first value of each range in the list represents the lower bound of the range (with ‘infinite’ representing negative infinity), and the second value represents the upper bound of the range (with ‘infinite’ representing positive infinity). This is an inclusive range - it includes both the lower and upper bound numbers. The range of the counter style as a whole is the union of the individual ranges. If the lower bound of any range is higher than the higher bound, the entire descriptor is invalid and must be ignored.

If the value is ‘auto’, the range depends on the counter type. For ‘repeating’, ‘numeric’, and ‘non-repeating’ types, it must be treated identically to specifying ‘infinite infinite’. For ‘alphabetic’ and ‘symbolic’, it must be treated identically to ‘1 infinite’. For ‘additive’, it must be treated identically to ‘0 infinite’. For ‘override’, it must be treated according to the type of the counter style it is overriding.

Some counter style types have their own implicit ranges, specified above in the individual descriptions for each type. The explicit range given by the ‘range’ descriptor applies at the same time as the implicit range given by the ‘type’ descriptor - if the counter value is outside either range, the fallback style must instead be used to generate the representation.

There's also an implicit range coming from implementation limits. Should we require UAs to support all values in a signed 2-byte int, or a signed 4-byte int?

10.6. Defining fallback: the ‘fallback’ descriptor

Name: fallback
Value: <counter-style-name>
Initial: decimal

The ‘fallback’ descriptor specifies a fallback counter style to be used when the current counter style can't create a representation for a given counter value. For example, if a counter style defined with a range of 1-10 is asked to represent a counter value of 11, the counter value's representation is instead constructed with the fallback counter style (or possibly the fallback style's fallback style, if the fallback style can't represent that value, etc.).

If the value of the ‘fallback’ descriptor isn't the name of any currently-defined counter style, the used value of the ‘fallback’ descriptor is decimal instead. Similarly, while following fallbacks to find a counter style that can render the given counter value, if a loop in the specified fallbacks is detected, the decimal style must be used instead.

Note that it is not necessarily an error to specify fallback loops. For example, if an author desires a counter style with significantly different representations for even and odd counter values, they may find it easiest to define one style that can only represent odd values and one that can only represent even values, and specify each as the fallback for the other one. Though the fallback graph is circular, at no point do you encounter a loop while following these fallbacks - every counter value is represented by one or the other counter style. Is it useful to allow this case? If it would be significantly easier for implementations to just detect and reject circular fallback graphs, that would probably be acceptable.

10.7. Marker characters: the ‘symbols’ and ‘additive-symbols’ descriptors

Name: symbols
Value: [ <string> | <image> | <identifier> ]+
Initial: N/A
Name: additive-symbols
Value: [ <integer> && [ <string> | <image> | <identifier> ] ]#
Initial: N/A

The ‘symbols’ and ‘additive-symbols’ descriptors specify the characters used by the marker-construction algorithm specified by the ‘type’ descriptor. The ‘symbols’ descriptor must be specified if the counter type is repeating, numeric, alphabetic, symbolic, or non-repeating, and the ‘additive-symbols’ descriptor must be specified if the counter type is additive; otherwise, the @counter-style is invalid and must be ignored.

Some counter styles specify that the ‘symbols’ descriptor must have at least two entries. If the counter's style is such a type, and the ‘symbols’ descriptor has only a single entry, the counter style is invalid and must be ignored.

Each entry in the ‘symbols’ descriptor's value defines a counter symbol, which is interpreted differently based on the counter style's type. Each entry in the ‘additive-symbols’ descriptor's value defines an additive tuple, which consists of a counter symbol and a non-negative integer weight. Each weight must be a non-negative integer, and the additive tuples must be specified in order of descending weight; otherwise, the @counter-style is invalid and must be ignored.

Counter symbols may be strings, images, or identifiers, and the three types can be mixed in a single descriptor. Counter representations are constructed by concatenating counter symbols together. Identifiers are rendered as strings containing the same characters. Images are rendered as inline replaced elements. The default object size of an image counter symbol is a 1em by 1em square.

11. Defining Anonymous Counter Styles: the ‘symbols()’ function

The previous chapter specified a way to define custom counter styles. However, counter styles are sometimes used only once in a stylesheet, and defining a full ‘@counter-style’ rule can be overkill for this case (not to mention the possibility of unintentional name collisions). To address this case, the ‘symbols()’ function provides a simple way to define an anonymous counter style as an inline value. It does not provide the full feature-set of the ‘@counter-style’ rule, but provides a sufficient subset to still be useful. The syntax of the ‘symbols()’ rule is:

<symbols-function> = symbols( <type>? [ <string> | <image> ]+ )

Where <type> is one of the following keywords: ‘repeating’, ‘numeric’, ‘alphabetic’, ‘symbolic’, or ‘non-repeating’.

The ‘symbols()’ function defines an anonymous counter style with no name, a prefix and suffix of ‘""’ (the empty string), a range from negative infinity to positive infinity, an fallback style of ‘decimal’, and a negative sign of "\2D" ("-" hyphen-minus). The counter style's algorithm is constructed by consulting the previous chapter using the provided type - or ‘symbolic’ if the type was omitted - and the provided <string>s and <image>s as the value of the ‘symbols’ property. If the type is ‘non-repeating’, the first symbol value is ‘1’.

This code:

ol { list-style: symbols("*" "\2020" "\2021" "\A7"); }

will produce lists that look like:

*   One
†   Two
‡   Three
§   Four
**  Five
††  Six
‡‡  Seven

On the other hand, specifying the type of counter, like so:

ol { list-style: symbols(repeating "*" "\2020" "\2021" "\A7"); }

will produce lists that look like:

*   One
†   Two
‡   Three
§   Four
*   Five
†   Six
‡   Seven

Note that the ‘symbols()’ function only allows strings and images, while the ‘symbols’ descriptor of a ‘@counter-style’ rule also allows identifiers.

12. Predefined Counter Styles

The following stylesheet redefines all of the counter styles defined in CSS 2.1 using the ‘@counter-style’ rule. This stylesheet is normative - UAs must include it in their UA stylesheet.

In addition to the 2.1 counter styles, a large number of additional counter styles are defined in the Additional Predefined Counter Styles for CSS document. The majority of the additional counter styles are additional alphabetic or numeric styles for various world languages. The normative status of that document is currently undecided, but at minimum it provides a large resource of counter styles which can be copied into an author's stylesheet.


@counter-style decimal-leading-zero {
	type: non-repeating -9;
	symbols: '-09' '-08' '-07' '-06' '-05' '-04' '-03' '-02' '-01' '00' '01' '02' '03' '04' '05' '06' '07' '08' '09';
}

@counter-style lower-roman {
	type: additive;
	range: 1 4999;
	additive-symbols: 1000 m, 900 cm, 500 d, 400 cd, 100 c, 90 xc, 50 l, 40 xl, 10 x, 9 ix, 5 v, 4 iv, 1 i;
}

@counter-style upper-roman {
	type: additive;
	range: 1 4999;
	additive-symbols: 1000 M, 900 CM, 500 D, 400 CD, 100 C, 90 XC, 50 L, 40 XL, 10 X, 9 IX, 5 V, 4 IV, 1 I;
}

@counter-style georgian {
	type: additive;
	range: 1 19999;
	additive-symbols: 10000 '\10F5', 9000 '\10F0', 8000 '\10EF', 7000 '\10F4', 6000 '\10EE', 5000 '\10ED', 4000 '\10EC', 3000 '\10EB', 2000 '\10EA', 1000 '\10E9', 900 '\10E8', 800 '\10E7', 700 '\10E6', 600 '\10E5', 500 '\10E4', 400 '\10F3', 300 '\10E2', 200 '\10E1', 100 '\10E0', 90 '\10DF', 80 '\10DE', 70 '\10DD', 60 '\10F2', 50 '\10DC', 40 '\10DB', 30 '\10DA', 20 '\10D9', 10 '\10D8', 9 '\10D7', 8 '\10F1', 7 '\10D6', 6 '\10D5', 5 '\10D4', 4 '\10D3', 3 '\10D2', 2 '\10D1', 1 '\10D0';
	/* 10000 'ჵ', 9000 'ჰ', 8000 'ჯ', 7000 'ჴ', 6000 'ხ', 5000 'ჭ', 4000 'წ', 3000 'ძ', 2000 'ც', 1000 'ჩ', 900 'შ', 800 'ყ', 700 'ღ', 600 'ქ', 500 'ფ', 400 'ჳ', 300 'ტ', 200 'ს', 100 'რ', 90 'ჟ', 80 'პ', 70 'ო', 60 'ჲ', 50 'ნ', 40 'მ', 30 'ლ', 20 'კ', 10 'ი', 9 'თ', 8 'ჱ', 7 'ზ', 6 'ვ', 5 'ე', 4 'დ', 3 'გ', 2 'ბ', 1 'ა' */
}

@counter-style armenian {
	type: additive;
	range: 1 9999;
	additive-symbols: 9000 '\554', 8000 '\553', 7000 '\552', 6000 '\551', 5000 '\550', 4000 '\54F', 3000 '\54E', 2000 '\54D', 1000 '\54C', 900 '\54B', 800 '\54A', 700 '\549', 600 '\548', 500 '\547', 400 '\546', 300 '\545', 200 '\544', 100 '\543', 90 '\542', 80 '\541', 70 '\540', 60 '\53F', 50 '\53E', 40 '\53D', 30 '\53C', 20 '\53B', 10 '\53A', 9 '\539', 8 '\538', 7 '\537', 6 '\536', 5 '\535', 4 '\534', 3 '\533', 2 '\532', 1 '\531';
	/* 9000 'Ք', 8000 'Փ', 7000 'Ւ', 6000 'Ց', 5000 'Ր', 4000 'Տ', 3000 'Վ', 2000 'Ս', 1000 'Ռ', 900 'Ջ', 800 'Պ', 700 'Չ', 600 'Ո', 500 'Շ', 400 'Ն', 300 'Յ', 200 'Մ', 100 'Ճ', 90 'Ղ', 80 'Ձ', 70 'Հ', 60 'Կ', 50 'Ծ', 40 'Խ', 30 'Լ', 20 'Ի', 10 'Ժ', 9 'Թ', 8 'Ը', 7 'Է', 6 'Զ', 5 'Ե', 4 'Դ', 3 'Գ', 2 'Բ', 1 'Ա' */
}

@counter-style lower-alpha {
	type: alphabetic;
	symbols: a b c d e f g h i j k l m n o p q r s t u v w x y z;
}

@counter-style lower-latin {
	type: override lower-alpha;
}

@counter-style upper-alpha {
	type: alphabetic;
	symbols: A B C D E F G H I J K L M N O P Q R S T U V W X Y Z;
}

@counter-style upper-latin {
	type: override upper-alpha;
}

@counter-style lower-greek {
	type: alphabetic;
	symbols: '\3B1' '\3B2' '\3B3' '\3B4' '\3B5' '\3B6' '\3B7' '\3B8' '\3B9' '\3BA' '\3BB' '\3BC' '\3BD' '\3BE' '\3BF' '\3C0' '\3C1' '\3C3' '\3C4' '\3C5' '\3C6' '\3C7' '\3C8' '\3C9';
	/* 'α' 'β' 'γ' 'δ' 'ε' 'ζ' 'η' 'θ' 'ι' 'κ' 'λ' 'μ' 'ν' 'ξ' 'ο' 'π' 'ρ' 'σ' 'τ' 'υ' 'φ' 'χ' 'ψ' 'ω' */
}

In addition to the above styles, UAs must define three counter styles named ‘disc’, ‘circle’, and ‘square’. They must do so either by including the following stylesheet in their UA stylesheet, or by rendering the bullets with a browser-generated image matching the descriptions below the stylesheet.

If a browser chooses to render these counter styles by generating images, the images must be sized to attractively fill a 1em by 1em rectangle. As well, the styles must still be overrideable by a later ‘@counter-style’ rule defining a counter style of the same name, as if they were defined by a ‘@counter-style’ rule in the UA stylesheet.

@counter-style disc {
	type: repeating;
	symbols: '\2022';
	/* '•' */
	suffix: '';
}

@counter-style circle {
	type: repeating;
	symbols: '\25E6';
	/* '◦' */
	suffix: '';
}

@counter-style square {
	type: repeating;
	symbols: '\25FE';
	/* '◾' */
	suffix: '';
}
disc
A filled circle, similar to • U+2022 BULLET.
circle
A hollow circle, similar to ◦ U+25E6 WHITE BULLET.
square
A filled square, similar to ◾ U+25FE BLACK MEDIUM SMALL SQUARE.

Finally, UAs must define a ‘decimal’ style with behavior matching the following stylesheet fragment, as if it were defined by a ‘@counter-style’ rule in the UA stylesheet. This style is defined specially because naming a counter style ‘decimal’ is a syntax error, so it can't actually be defined just by including it in the UA stylesheet. This restriction exists so that ‘decimal’ is always present as a valid fallback type, regardless of what an author or user defines in their own stylesheets.

@counter-style decimal {
	type: numeric;
	symbols: '0' '1' '2' '3' '4' '5' '6' '7' '8' '9';
}

Do we need to predefine the styles that are present in CSS2 but didn't make it to CSS2.1? They're all present in the "Additional Predefined Counter Styles" document.

13. Sample style sheet for HTML

This section is informative, not normative. HTML itself defines the actual default properties that apply to HTML lists.

/* Set up list items */
li { 
	display: list-item; 
	/* counter-increment: list-item; (implied by display: list-item) */ 
}

/* Set up ol and ul so that they reset the list-item counter */
ol, ul { 
	counter-reset: list-item; 
}

/* Default list style types for ordered lists */
ol { 
	list-style-type: decimal; 
}

/* Default list style types for unordered lists up to 3 deep */
ul { list-style-type: disc; }
ul ul { list-style-type: square; }
ul ul ul { list-style-type: circle; }
/* Alternately, if Values & Units Level 3 is supported, replace
   the above three lines with: */
ul { list-style-type: cycle(disc, square, circle); }

/* The type attribute on ol and ul elements */
ul[type="disc"] { list-style-type: disc; }
ul[type="circle"] { list-style-type: circle; }
ul[type="square"] { list-style-type: square; }
ol[type="1"] { list-style-type: decimal; }
ol[type="a"] { list-style-type: lower-alpha; }
ol[type="A"] { list-style-type: upper-alpha; }
ol[type="i"] { list-style-type: lower-roman; }
ol[type="I"] { list-style-type: upper-roman; }

/* The start attribute on ol elements */
ol[start] { 
	counter-reset: list-item attr(start, integer, 1); 
	counter-increment: list-item -1; 
}

/* The value attribute on li elements */
li[value] { 
	counter-set: list-item attr(value, integer, 1); 
	counter-increment: none; /* Turn off default increase */
}

/* Box Model Rules */
ol, ul { 
	display: block; 
	margin: 1em 0; 
	padding-left: 40px;
	marker-attachment: list-container;
}
 
ol ol, ol ul, ul ul, ul ol { 
	margin-top: 0; 
	margin-bottom: 0; 
}

li {
	text-align: match-parent;
}
 
li::marker { 
	display: inline;
	margin-right: 1em;
	text-align: end;
	unicode-bidi: isolate;
	/* 'position' computes to "static" or "marker" depending on list-style-position */
}
	

Profiles

This module has two profiles: CSS Level 1 and Full. There is no CSS2 profile because this module is incompatible with the CSS2 list model.

The CSS Level 1 module consists of ‘list-style’, ‘list-style-position’, ‘list-style-image’, and ‘list-style-type’ (but only the following values: ‘disc’, ‘circle, square’, ‘decimal’, ‘lower-roman’, ‘upper-roman’, ‘lower-alpha’, ‘upper-alpha’, ‘none’). It does not include the ::marker pseudo element.

The Full profile contains everything.

Acknowledgments

The following people and documentation they wrote were very useful for defining the numbering systems: Alexander Savenkov, Arron Eicholz, Aryeh Gregor, Frank Tang, Jonathan Rosenne, Karl Ove Hufthammer, Musheg Arakelyan, Nariné Renard Karapetyan, Randall Bart, Richard Ishida, Simon Montagu (Mozilla, smontagu@smontagu.org)

Changes From CSS2.1

As described in the introduction section, there are significant changes in this module when compared to CSS2.1.

  1. The ::marker pseudo-element has been introduced to allow styling of the list marker directly.
  2. The ‘marker’ value for ‘position’ has been added, to allow elements in the document to position themselves similarly to how markers do.
  3. Many new list style types have been added, along with explicit algorithms for each.
  4. The list-item predefined counter identifier has been introduced.

References

Normative references

[CSS21]
Bert Bos; et al. Cascading Style Sheets Level 2 Revision 1 (CSS 2.1) Specification. 7 June 2011. W3C Recommendation. URL: http://www.w3.org/TR/2011/REC-CSS2-20110607

Other references

[CSS1]
Håkon Wium Lie; Bert Bos. Cascading Style Sheets (CSS1) Level 1 Specification. 11 April 2008. W3C Recommendation. URL: http://www.w3.org/TR/2008/REC-CSS1-20080411

Property index

Property Values Initial Applies to Inh. Percentages Media
counter-increment [ <identifier> <integer>? ]+ | none none all elements no N/A all
counter-reset [ <identifier> <integer>? ]+ | none none all elements no N/A all
counter-set [ <identifier> <integer>? ]+ | none none all elements no N/A all
list-style <‘list-style-type’> || <‘list-style-position’> || <‘list-style-image’> see individual properties list items yes N/A visual
list-style-image <image> | none none list items yes N/A visual
list-style-position inside | outside outside list items yes N/A visual
list-style-type <string> | <counter-style> | none disc list items yes N/A visual
‘display’ inline-list-item same as CSS2.1 same as CSS2.1 same as CSS2.1 same as CSS2.1 same as CSS2.1
‘position’ ‘marker’ same as CSS2.1 same as CSS2.1 same as CSS2.1 same as CSS2.1 same as CSS2.1
marker-attachment list-item | list-container list-item list items yes N/A visual

Index