To display complete section paths such as 1.2.1, nest ordered lists and use CSS counters() to join their levels. This tutorial creates a local HTML file on a Mac, preserves the outline’s order, and shows how to check numbering after edits.

How does full-path numbering work?

A complete section path records an item’s position at every level. For example, 1.2.1 means the first item within the second child of the first top-level item. CSS provides counters() specifically to combine nested counter values, reading from the outermost counter to the innermost and inserting your chosen separator. MDN: counters() CSS function

For this task, the outline is an ordered list with nested branches. It is not an automatically generated table of contents for headings elsewhere in a document. HTML defines an ordered list’s items in tree order, so keep the source order equal to the intended reading order. HTML Standard: The ol element

The example uses three rules: create a counter on every participating list, increment it for each item, and display the combined path before the item’s label. This follows MDN’s documented nested-counter pattern, with selectors restricted to this outline. MDN: Using CSS counters

Create the outline file on your Mac

You need TextEdit and a browser such as Safari. The complete example contains its own stylesheet and synthetic sample content; it requires no packages, build tools, or JavaScript.

  1. Open TextEdit and choose File > New.
  2. Choose Format > Make Plain Text.
  3. Paste the complete example below.
  4. Choose File > Save, name the file outline.html, and save it somewhere convenient, such as your Desktop.
  5. If TextEdit asks which extension to use, choose Use .html.

Apple documents this plain-text workflow for creating HTML files in TextEdit. Apple: Work with HTML documents in TextEdit on Mac

The following is a complete document. Copy the contents of the code block, leaving out the Markdown fence lines.

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Nested outline example</title>
  <style>
    body {
      max-width: 48rem;
      margin: 2rem auto;
      padding: 0 1rem;
      font-family: system-ui, sans-serif;
      line-height: 1.6;
    }

    .outline,
    .outline ol {
      counter-reset: outline-section;
      list-style-type: none;
      padding-inline-start: 1.5rem;
    }

    .outline li {
      counter-increment: outline-section;
      margin-block: 0.35rem;
    }

    .outline li::before {
      content: counters(outline-section, ".") " ";
      font-weight: 600;
    }
  </style>
</head>
<body>
  <h1>Sample document outline</h1>
  <ol class="outline" role="list">
    <li>Node A
      <ol role="list">
        <li>Node B</li>
        <li>Node C
          <ol role="list">
            <li>Node D</li>
            <li>Node E</li>
          </ol>
        </li>
        <li>Node F</li>
      </ol>
    </li>
    <li>Node G
      <ol role="list">
        <li>Node H</li>
      </ol>
    </li>
    <li>Node I</li>
  </ol>
</body>
</html>

Open Safari, then drag outline.html from Finder into a Safari window. Use TextEdit to edit the source and Safari to inspect the rendered outline. After each edit, save the file and reload the browser page.

Keep TextEdit in code-editing mode

When reopening the file, you may see formatted content rather than source code. Apple documents two options: use File > Open, click Show Options, and select Ignore rich text commands; or enable Display HTML files as HTML code instead of formatted text under TextEdit > Settings > Open and Save. Apple: Work with HTML documents in TextEdit on Mac

Check the complete paths against the sample

The expected visible outline is below. The labels are neutral placeholders; their positions determine the numbers.

1 Node A
  1.1 Node B
  1.2 Node C
    1.2.1 Node D
    1.2.2 Node E
  1.3 Node F
2 Node G
  2.1 Node H
3 Node I

This sample checks three distinct transitions. Node D starts a third level under Node C. Node F resumes the second level after that branch finishes. Node G resumes the top level, and its first child starts at 2.1. A deterministic traversal of the sample tree confirmed these expected paths; the example was not browser-rendered during preparation.

Why each CSS rule matters

The reset belongs on both .outline and .outline ol. Every nested list needs a new instance of outline-section; resetting only the outer list would not establish a separate count for each depth. Incrementing on each list item advances the current instance. MDN: Using CSS counters

The display rule uses counters(outline-section, "."). The plural function collects all active instances with that name. Replacing it with singular counter(outline-section) would display only the current level’s value, leaving a nested item labeled 1 rather than its complete path. MDN: counters() CSS function

The example removes the native list markers because it supplies its own prefixes. Each list also has role="list": MDN documents that Safari can otherwise omit lists styled with list-style-type: none from its accessibility tree. Keep that attribute when adding another nested list. MDN: list-style-type CSS property

Edit the outline without breaking its structure

Put each nested <ol> inside the <li> that owns the branch. For example, Node C contains its child list before Node C’s closing </li>. A nested list must not be a direct child of another ordered list. MDN: HTML ordered list element

For a practical edit test, add a new item immediately before Node C. Its new path should be 1.2; Node C should become 1.3; and Node D and Node E should become 1.3.1 and 1.3.2. Node G should remain 2. These expected changes follow from the sample’s hierarchy and provide a useful check that numbering follows the branch structure.

To move a branch, move the entire owning list item, including its nested lists and closing tag. Moving only its label leaves its children behind. To create a fourth level, repeat the existing nested-list structure inside the appropriate item; do not type an extra number into the label.

Keep one child list per branch when its children should form one continuous sequence. In this implementation, creating another sibling child list creates another reset, so its local numbering starts again. MDN’s example demonstrates this restart with separate nested lists under the same item. MDN: Using CSS counters

Diagnose incorrect numbering in a useful order

Start with structure, then counter rules, then presentation. Changing indentation cannot repair a missing nesting level.

What you see What to inspect Correction for this example
Every nested item shows a single number The function in content Use plural counters() with the period separator.
Every sibling starts at 1 The reset selector Reset on lists, not on every item.
Native numbers appear beside full paths The list-style rule Apply list-style-type: none to the outer and nested lists.
A branch has an unexpected prefix Placement of its nested list Keep the child list inside its owning item.
Another list elsewhere gains prefixes Counter selector scope Keep the .outline prefix on the rules.

Also check whether an item has been hidden. CSS counter operations do not run on elements that generate no box, including elements with display: none. By contrast, visibility: hidden still generates a box and can therefore affect counting. Choose deliberately whether an omitted item should occupy a position. W3C: Counters in elements that do not generate boxes

What about custom starting numbers?

This example deliberately uses a custom counter starting from zero and incrementing to one. HTML attributes such as start, reversed, and a list item’s value control native ordered-list numbering; the custom outline-section rules shown here do not read those attributes. Adding start="4" alone therefore does not make this example’s displayed prefix begin at 4. HTML Standard: Ordered lists and list items

If you need discontinuous numbering or descending branches, establish that requirement before adapting this stylesheet. Keep the displayed path consistent with the intended ordered-list values instead of adding attributes that the custom display ignores.

Decide whether the numbers are presentation or document content

The prefixes in this tutorial are CSS-generated content. They are appropriate for a visual outline, but essential references need additional consideration: MDN notes that content added through ::before is not reliably available to screen readers. Adding role="list" preserves list recognition; it does not guarantee announcement of every generated path. MDN: ::before CSS pseudo-element

If a complete path must be part of the actual text—for example, in a plain-text export—include it as text in the document or generate it during an export step. That changes the maintenance contract: edits must update those stored paths as well. Do not rely on a visual prefix to supply essential text in every output format.

For a local visual outline, finish by checking the nine sample paths, inserting a sibling, and confirming the affected branch renumbers as expected. Once those checks pass in your browser, replace the synthetic labels while preserving the nesting and source order.

Sources