Nested lists in CSS with a numbered hierarchy

Using CSS, we can make nested ordered lists automatically display in a numbered hierarchy—like "1, 1.1, 1.2, 2, 2.1", without any additional HTML markup changes. Here’s how to set it up!

The Confident Logo, Drupal Agency led by Mark Conroy

Finished Product

Basic HTML Structure for Nested Lists

First, we need to ensure our HTML has the right structure. Here’s an example of what a nested ordered list should look like:

<ol>
  <li>Main Item 1
    <ol>
      <li>Sub-item 1.1</li>
      <li>Sub-item 1.2</li>
    </ol>
  </li>
  <li>Main Item 2
    <ol>
      <li>Sub-item 2.1</li>
    </ol>
  </li>
</ol>

In this example:

  • The outer <ol> represents the main list.
  • Each <li> inside it represents a main item.
  • Nested <ol> tags represent sub-items.

Now, let's apply CSS to format these numbers!

Setting Up CSS for Nested Counters

To achieve the numbering format (1, 1.1, 1.2, 2, 2.1), we’ll use CSS counters. CSS counters are like variables that increment every time an element appears. We can reset and increment these counters as we go deeper into the nested list levels.

Here’s the CSS to achieve this structure:

/* Reset the counter on the main ordered list */
ol {
  counter-reset: section;
}
/* Style for the main list items */
ol > li {
  counter-increment: section;
}
/* Before each list item in the main list, insert the counter */
ol > li::before {
  content: counter(section) ". ";
}
/* For nested lists (2nd level), add a new counter and reset */
ol > li > ol {
  counter-reset: subsection;
}
/* For nested list items, increment the subsection counter */
ol > li > ol > li {
  counter-increment: subsection;
}
/* Format the second-level list items with main section and subsection */
ol > li > ol > li::before {
  content: counter(section) "." counter(subsection) " ";
}

Explanation of the CSS Code:

  1. counter-reset: section;
    This resets the counter named "section" at the start of the main ordered list.
  2. counter-increment: section;
    Every time we encounter a direct child <li> inside the main <ol>, the "section" counter increments by 1.
  3. content: counter(section) ". ";
    Adds the main section number (e.g., "1.") before each main list item.
  4. counter-reset: subsection;
    Resets the "subsection" counter for every nested <ol> (second level) so that each new main section starts fresh.
  5. counter-increment: subsection;
    Increments the "subsection" counter each time a <li> is added to the nested list.
  6. content: counter(section) "." counter(subsection) " ";
    Combines the section and subsection counters to form a hierarchical format (e.g., "1.1") before each nested item.

Want more levels?

Expanding our CSS to support three levels of nested lists is straightforward by adding an additional counter for the third level. This allows us to have a numbering structure like "2.3.2" or "2.3.3." Let's dive into the updated code and explain how it works.

HTML Structure for Three-Level Nested Lists

Our HTML structure will now include an additional level of nested ordered lists. Here’s an example:

<ol>
  <li>Main Item 1
    <ol>
      <li>Sub-item 1.1
        <ol>
          <li>Sub-sub-item 1.1.1</li>
          <li>Sub-sub-item 1.1.2</li>
        </ol>
      </li>
      <li>Sub-item 1.2</li>
    </ol>
  </li>
  <li>Main Item 2
    <ol>
      <li>Sub-item 2.1</li>
      <li>Sub-item 2.2
        <ol>
          <li>Sub-sub-item 2.2.1</li>
          <li>Sub-sub-item 2.2.2</li>
        </ol>
      </li>
    </ol>
  </li>
</ol>

In this example:

  • The first <ol> represents the main list.
  • Nested <ol> elements represent the second level of items.
  • An additional nested <ol> inside a second-level <li> represents the third level of items.

CSS for Three-Level Nested Counters

To achieve three-level numbering (e.g., 2.3.2), we need to add another counter for the third level and adjust the styles accordingly.

/* Reset the counter on the main ordered list */
ol {
  counter-reset: section;
}
/* Style for main list items (first level) */
ol > li {
  counter-increment: section;
}
/* First level - add section number before each main list item */
ol > li::before {
  content: counter(section) ". ";
}
/* Second level - reset the subsection counter for nested lists */
ol > li > ol {
  counter-reset: subsection;
}
/* Second level - increment subsection counter */
ol > li > ol > li {
  counter-increment: subsection;
}
/* Format for second-level items with section and subsection numbers */
ol > li > ol > li::before {
  content: counter(section) "." counter(subsection) " ";
}
/* Third level - reset the sub-subsection counter for third-level nested lists */
ol > li > ol > li > ol {
  counter-reset: subsubsection;
}
/* Third level - increment sub-subsection counter */
ol > li > ol > li > ol > li {
  counter-increment: subsubsection;
}
/* Format for third-level items with section, subsection, and sub-subsection numbers */
ol > li > ol > li > ol > li::before {
  content: counter(section) "." counter(subsection) "." counter(subsubsection) " ";
}

Explanation of the Updated CSS Code:

  1. Main Level (1st level)
    • counter-reset: section; - resets the main counter for the main list.
    • counter-increment: section; - increments the main counter for each item in the main list.
    • content: counter(section) ". "; - formats the main items as "1., 2., 3.," and so on.
  2. Second Level (2nd level)
    • counter-reset: subsection; - resets the subsection counter at each new second-level list.
    • counter-increment: subsection; - increments the subsection counter for each second-level item.
    • content: counter(section) "." counter(subsection) " "; - formats second-level items as "1.1, 1.2," etc., combining the main and subsection counters.
  3. Third Level (3rd level)
    • counter-reset: subsubsection; - resets the sub-subsection counter at each new third-level list.
    • counter-increment: subsubsection; - increments the sub-subsection counter for each third-level item.
    • content: counter(section) "." counter(subsection) "." counter(subsubsection) " "; - formats third-level items with the full hierarchy, such as "1.1.1, 1.1.2," and so on.

Final Thoughts

Now, your CSS setup supports three levels of nested lists with automatic numbering in the "1.1.1" format. This CSS structure is both flexible and scalable; if you need more levels, you can keep adding additional counters in a similar fashion. This styling approach keeps your HTML clean while ensuring that any nested lists are numbered accurately, making your content more readable and structured.

Happy coding!

Filed Under:

  1. CSS
  2. HTML
  3. Frontend Development