122

I use nested counters and scope to create an ordered list:

ol {
    counter-reset: item;
    padding-left: 10px;
}
li {
    display: block
}
li:before {
    content: counters(item, ".") " ";
    counter-increment: item
}
<ol>
    <li>one</li>
    <li>two</li>
    <ol>
        <li>two.one</li>
        <li>two.two</li>
        <li>two.three</li>
    </ol>
    <li>three</li>
    <ol>
        <li>three.one</li>
        <li>three.two</li>
        <ol>
            <li>three.two.one</li>
            <li>three.two.two</li>
        </ol>
    </ol>
    <li>four</li>
</ol>

I expect the following outcome:

1. one
2. two
  2.1. two.one
  2.2. two.two
  2.3. two.three
3. three
  3.1 three.one
  3.2 three.two
    3.2.1 three.two.one
    3.2.2 three.two.two
4. four

Instead, this is what I see (wrong numbering):

1. one
2. two
  2.1. two.one
  2.2. two.two
  2.3. two.three
2.4 three <!-- this is where it goes wrong, when going back to the parent -->
  2.1 three.one
  2.2 three.two
    2.2.1 three.two.one
    2.2.2 three.two.two
2.3 four

I have no clue, does anyone see where it goes wrong?

Here is a JSFiddle: http://jsfiddle.net/qGCUk/2/

isherwood
  • 58,414
  • 16
  • 114
  • 157
dirk
  • 2,206
  • 4
  • 21
  • 30

10 Answers10

132

Uncheck "normalize CSS" - http://jsfiddle.net/qGCUk/3/ The CSS reset used in that defaults all list margins and paddings to 0

UPDATE http://jsfiddle.net/qGCUk/4/ - you have to include your sub-lists in your main <li>

ol {
  counter-reset: item
}
li {
  display: block
}
li:before {
  content: counters(item, ".") " ";
  counter-increment: item
}
<ol>
  <li>one</li>
  <li>two
    <ol>
      <li>two.one</li>
      <li>two.two</li>
      <li>two.three</li>
    </ol>
  </li>
  <li>three
    <ol>
      <li>three.one</li>
      <li>three.two
        <ol>
          <li>three.two.one</li>
          <li>three.two.two</li>
        </ol>
      </li>
    </ol>
  </li>
  <li>four</li>
</ol>
KyleMit
  • 30,350
  • 66
  • 462
  • 664
Zoltan Toth
  • 46,981
  • 12
  • 120
  • 134
  • 4
    How to make its index's followed with point - like `1.` > `1.1.` `1.2.` `1.3.` and so on ? – URL87 May 28 '14 at 15:04
  • 2
    Just be sure to fix the css selectors so it doesn't affect things like navigation lists. – Okomikeruko Mar 14 '17 at 19:24
  • @Okomikeruko What does it mean to "fix the css selectors"? Because I'm running in exactly the problem you alluded to - this trick not just affecting the numbered list I want to use it for but the other lists in my HTML as well. :-\ Nevermind: Moshe Simantov's answer fixes that. :) – antred Feb 27 '20 at 12:42
  • 2
    @antred element attributes like `id` and `class` allow you to define css specific to those elements with selectors. If you use a blanket `li`, `ul`, `ol` etc, then the css affects all instances of it. But if you set your element to `
      ` and your css selector to `ol.cleared`, then you don't affect other `ol` elements unnecessarily.
    – Okomikeruko Feb 27 '20 at 13:40
  • Why is the li display: block here? – Joel Peltonen Jan 27 '21 at 12:17
  • This is such an ugly, hacky way. This method doesn't even keep the text to the right of the numbers; the text comes over to the number after the first line. It's not your fault though. It's so disappointing that CSS doesn't have a way to do this out of the box. – Paul Chris Jones Apr 01 '21 at 07:40
78

Use this style to change only the nested lists:

ol {
    counter-reset: item;
}

ol > li {
    counter-increment: item;
}

ol ol > li {
    display: block;
}

ol ol > li:before {
    content: counters(item, ".") ". ";
    margin-left: -20px;
}
Moshe Simantov
  • 3,937
  • 2
  • 25
  • 35
16

Check this out :

http://jsfiddle.net/PTbGc/

Your issue seems to have been fixed.


What shows up for me (under Chrome and Mac OS X)

1. one
2. two
  2.1. two.one
  2.2. two.two
  2.3. two.three
3. three
  3.1 three.one
  3.2 three.two
    3.2.1 three.two.one
    3.2.2 three.two.two
4. four

How I did it


Instead of :

<li>Item 1</li>
<li>Item 2</li>
   <ol>
        <li>Subitem 1</li>
        <li>Subitem 2</li>
   </ol>

Do :

<li>Item 1</li>
<li>Item 2
   <ol>
        <li>Subitem 1</li>
        <li>Subitem 2</li>
   </ol>
</li>
Dr.Kameleon
  • 22,532
  • 20
  • 115
  • 223
8

This is a great solution! With a few additional CSS rules you can format it just like an MS Word outline list with a hanging first line indent:

OL { 
  counter-reset: item; 
}
LI { 
  display: block; 
}
LI:before { 
  content: counters(item, ".") "."; 
  counter-increment: item; 
  padding-right:10px; 
  margin-left:-20px;
}
Tiffany
  • 680
  • 1
  • 15
  • 31
ChaosFreak
  • 101
  • 1
  • 2
  • 6
7

Keep it Simple!

This is a Simpler and Standard solution to increment the number and to retain the dot at the end.
Even if you get the CSS right, it will not work if your HTML is not correct. see below.

CSS

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

SASS

ol {
    counter-reset: item;
    li {
        display: block;
        &:before {
            content: counters(item, ". ") ". ";
            counter-increment: item
        }
    }
}

HTML Parent Child

If you add the child make sure it is under the parent li.

Will not work ✘

Notice the parent li and the child ol li are individual here, this will not work.

<ol>
    <li>Parent 1</li> <!-- Parent has open and close li tags -->
    <ol> 
        <li>Child</li>
    </ol>
    <li>Parent 2</li>
</ol>

Will work ✔

You need to place the ol li child element inside parent li. Notice the parent li is hugging the child.

<ol>

    <li>Parent 1 <!-- Parent open li tag -->
        <ol> 
            <li>Child</li>
        </ol>
    </li> <!-- Parent close li tag -->

    <li>Parent 2</li>
</ol>
Dexter
  • 7,911
  • 4
  • 41
  • 40
4

I think that these answers are over-complicating this. If you don't need to support Internet Explorer, then the solution is a one-liner:

ol > li::marker { content: counters(list-item, '.') '. '; }
<ol>
    <li>one</li>
    <li>two</li>
    <ol>
        <li>two.one</li>
        <li>two.two</li>
        <li>two.three</li>
    </ol>
    <li>three</li>
    <ol>
        <li>three.one</li>
        <li>three.two</li>
        <ol>
            <li>three.two.one</li>
            <li>three.two.two</li>
        </ol>
    </ol>
    <li>four</li>
</ol>

See the ::marker CSS pseudo-element page and the Using CSS counters page on MDN's CSS reference website for more information.

Tyler Crompton
  • 12,284
  • 14
  • 65
  • 94
3

After going through other answers I came up with this, just apply class nested-counter-list to root ol tag:

sass code:

ol.nested-counter-list {
  counter-reset: item;

  li {
    display: block;

    &::before {
      content: counters(item, ".") ". ";
      counter-increment: item;
      font-weight: bold;
    }
  }

  ol {
    counter-reset: item;

    & > li {
      display: block;

      &::before {
        content: counters(item, ".") " ";
        counter-increment: item;
        font-weight: bold;
      }
    }
  }
}

css code:

ol.nested-counter-list {
  counter-reset: item;
}
ol.nested-counter-list li {
  display: block;
}
ol.nested-counter-list li::before {
  content: counters(item, ".") ". ";
  counter-increment: item;
  font-weight: bold;
}
ol.nested-counter-list ol {
  counter-reset: item;
}
ol.nested-counter-list ol > li {
  display: block;
}
ol.nested-counter-list ol > li::before {
  content: counters(item, ".") " ";
  counter-increment: item;
  font-weight: bold;
}

ol.nested-counter-list {
  counter-reset: item;
}

ol.nested-counter-list li {
  display: block;
}

ol.nested-counter-list li::before {
  content: counters(item, ".") ". ";
  counter-increment: item;
  font-weight: bold;
}

ol.nested-counter-list ol {
  counter-reset: item;
}

ol.nested-counter-list ol>li {
  display: block;
}

ol.nested-counter-list ol>li::before {
  content: counters(item, ".") " ";
  counter-increment: item;
  font-weight: bold;
}
<ol class="nested-counter-list">
  <li>one</li>
  <li>two
    <ol>
      <li>two.one</li>
      <li>two.two</li>
      <li>two.three</li>
    </ol>
  </li>
  <li>three
    <ol>
      <li>three.one</li>
      <li>three.two
        <ol>
          <li>three.two.one</li>
          <li>three.two.two</li>
        </ol>
      </li>
    </ol>
  </li>
  <li>four</li>
</ol>

And if you need trailing . at the end of the nested list's counters use this:

ol.nested-counter-list {
  counter-reset: item;
}

ol.nested-counter-list li {
  display: block;
}

ol.nested-counter-list li::before {
  content: counters(item, ".") ". ";
  counter-increment: item;
  font-weight: bold;
}

ol.nested-counter-list ol {
  counter-reset: item;
}
<ol class="nested-counter-list">
  <li>one</li>
  <li>two
    <ol>
      <li>two.one</li>
      <li>two.two</li>
      <li>two.three</li>
    </ol>
  </li>
  <li>three
    <ol>
      <li>three.one</li>
      <li>three.two
        <ol>
          <li>three.two.one</li>
          <li>three.two.two</li>
        </ol>
      </li>
    </ol>
  </li>
  <li>four</li>
</ol>
Sushmit Sagar
  • 1,412
  • 2
  • 13
  • 27
1

I encountered similar problem recently. The fix is to set the display property of the li items in the ordered list to list-item, and not display block, and ensure that the display property of ol is not list-item. i.e

li { display: list-item;}

With this, the html parser sees all li as the list item and assign the appropriate value to it, and sees the ol, as an inline-block or block element based on your settings, and doesn't try to assign any count value to it.

1

Moshe's solution is great but the problem may still exist if you need to put the list inside a div. (read: CSS counter-reset on nested list)

This style could prevent that issue:

ol > li {
    counter-increment: item;
}

ol > li:first-child {
  counter-reset: item;
}

ol ol > li {
    display: block;
}

ol ol > li:before {
    content: counters(item, ".") ". ";
    margin-left: -20px;
}
<ol>
  <li>list not nested in div</li>
</ol>

<hr>

<div>
  <ol>
  <li>nested in div</li>
  <li>two
    <ol>
      <li>two.one</li>
      <li>two.two</li>
      <li>two.three</li>
    </ol>
  </li>
  <li>three
    <ol>
      <li>three.one</li>
      <li>three.two
        <ol>
          <li>three.two.one</li>
          <li>three.two.two</li>
        </ol>
      </li>
    </ol>
  </li>
  <li>four</li>
  </ol>
</div>

You can also set the counter-reset on li:before.

saboshi69
  • 689
  • 5
  • 12
0

Thank you everyone above for their answers!

As I needed RTL solution, I found out that this can solve it:

ol.nested-counter-list li {
  display: block;
  unicode-bidi: bidi-override;
}

So you would use any of the solution above, but also update the specific CSS selector for RTL cases.

nadavkav
  • 456
  • 7
  • 9