4.4.11 Headings and sections

The h1h6 elements and the hgroup element are headings.

The first element of heading content in an element of sectioning content represents the heading for that section. Subsequent headings of equal or higher rank start new (implied) sections, headings of lower rank start implied subsections that are part of the previous one. In both cases, the element represents the heading of the implied section.

Certain elements are said to be sectioning roots, including blockquote and td elements. These elements can have their own outlines, but the sections and headings inside these elements do not contribute to the outlines of their ancestors.

Sectioning content elements are always considered subsections of their nearest ancestor sectioning root or their nearest ancestor element of sectioning content, whichever is nearest, regardless of what implied sections other headings may have created.

For the following fragment:

<body>
 <h1>Foo</h1>
 <h2>Bar</h2>
 <blockquote>
  <h3>Bla</h3>
 </blockquote>
 <p>Baz</p>
 <h2>Quux</h2>
 <section>
  <h3>Thud</h3>
 </section>
 <p>Grunt</p>
</body>

...the structure would be:

  1. Foo (heading of explicit body section, containing the "Grunt" paragraph)
    1. Bar (heading starting implied section, containing a block quote and the "Baz" paragraph)
    2. Quux (heading starting implied section with no content other than the heading itself)
    3. Thud (heading of explicit section section)

Notice how the section ends the earlier implicit section so that a later paragraph ("Grunt") is back at the top level.

Sections may contain headings of any rank, but authors are strongly encouraged to either use only h1 elements, or to use elements of the appropriate rank for the section's nesting level.

Authors are also encouraged to explicitly wrap sections in elements of sectioning content, instead of relying on the implicit sections generated by having multiple headings in one element of sectioning content.

For example, the following is correct:

<body>
 <h4>Apples</h4>
 <p>Apples are fruit.</p>
 <section>
  <h2>Taste</h2>
  <p>They taste lovely.</p>
  <h6>Sweet</h6>
  <p>Red apples are sweeter than green ones.</p>
  <h1>Color</h1>
  <p>Apples come in various colors.</p>
 </section>
</body>

However, the same document would be more clearly expressed as:

<body>
 <h1>Apples</h1>
 <p>Apples are fruit.</p>
 <section>
  <h2>Taste</h2>
  <p>They taste lovely.</p>
  <section>
   <h3>Sweet</h3>
   <p>Red apples are sweeter than green ones.</p>
  </section>
 </section>
 <section>
  <h2>Color</h2>
  <p>Apples come in various colors.</p>
 </section>
</body>

Both of the documents above are semantically identical and would produce the same outline in compliant user agents.

This third example is also semantically identical, and might be easier to maintain (e.g. if sections are often moved around in editing):

<body>
 <h1>Apples</h1>
 <p>Apples are fruit.</p>
 <section>
  <h1>Taste</h1>
  <p>They taste lovely.</p>
  <section>
   <h1>Sweet</h1>
   <p>Red apples are sweeter than green ones.</p>
  </section>
 </section>
 <section>
  <h1>Color</h1>
  <p>Apples come in various colors.</p>
 </section>
</body>

This final example would need explicit style rules to be rendered well in legacy browsers. Legacy browsers without CSS support would render all the headings as top-level headings.

4.4.11.1 Creating an outline

This section defines an algorithm for creating an outline for a sectioning content element or a sectioning root element. It is defined in terms of a walk over the nodes of a DOM tree, in tree order, with each node being visited when it is entered and when it is exited during the walk.

The outline for a sectioning content element or a sectioning root element consists of a list of one or more potentially nested sections. A section is a container that corresponds to some nodes in the original DOM tree. Each section can have one heading associated with it, and can contain any number of further nested sections. The algorithm for the outline also associates each node in the DOM tree with a particular section and potentially a heading. (The sections in the outline aren't section elements, though some may correspond to such elements — they are merely conceptual sections.)

The following markup fragment:

<body>
 <h1>A</h1>
 <p>B</p>
 <h2>C</h2>
 <p>D</p>
 <h2>E</h2>
 <p>F</p>
</body>

...results in the following outline being created for the body node (and thus the entire document):

  1. Section created for body node.

    Associated with heading "A".

    Also associated with paragraph "B".

    Nested sections:

    1. Section implied for first h2 element.

      Associated with heading "C".

      Also associated with paragraph "D".

      No nested sections.

    2. Section implied for second h2 element.

      Associated with heading "E".

      Also associated with paragraph "F".

      No nested sections.

The algorithm that must be followed during a walk of a DOM subtree rooted at a sectioning content element or a sectioning root element to determine that element's outline is as follows:

  1. Let current outlinee be null. (It holds the element whose outline is being created.)

  2. Let current section be null. (It holds a pointer to a section, so that elements in the DOM can all be associated with a section.)

  3. Create a stack to hold elements, which is used to handle nesting. Initialize this stack to empty.

  4. As you walk over the DOM in tree order, starting with the sectioning content element or sectioning root element at the root of the subtree for which an outline is to be created, trigger the first relevant step below for each element as you enter and exit it.

    If you are exiting an element and that element is the element at the top of the stack

    The element being exited is a heading content element.

    Pop that element from the stack.

    If the top of the stack is a heading content element

    Do nothing.

    When entering a sectioning content element or a sectioning root element

    If current outlinee is not null, and the current section has no heading, create an implied heading and let that be the heading for the current section.

    If current outlinee is not null, push current outlinee onto the stack.

    Let current outlinee be the element that is being entered.

    Let current section be a newly created section for the current outlinee element.

    Associate current outlinee with current section.

    Let there be a new outline for the new current outlinee, initialized with just the new current section as the only section in the outline.

    When exiting a sectioning content element, if the stack is not empty

    Pop the top element from the stack, and let the current outlinee be that element.

    Let current section be the last section in the outline of the current outlinee element.

    Append the outline of the sectioning content element being exited to the current section. (This does not change which section is the last section in the outline.)

    When exiting a sectioning root element, if the stack is not empty

    Run these steps:

    1. Pop the top element from the stack, and let the current outlinee be that element.

    2. Let current section be the last section in the outline of the current outlinee element.

    3. Finding the deepest child: If current section has no child sections, stop these steps.

    4. Let current section be the last child section of the current current section.

    5. Go back to the substep labeled finding the deepest child.

    When exiting a sectioning content element or a sectioning root element

    The current outlinee is the element being exited, and it is the sectioning content element or a sectioning root element at the root of the subtree for which an outline is being generated.

    Let current section be the first section in the outline of the current outlinee element.

    Skip to the next step in the overall set of steps. (The walk is over.)

    When entering a heading content element

    If the current section has no heading, let the element being entered be the heading for the current section.

    Otherwise, if the element being entered has a rank equal to or greater than the heading of the last section of the outline of the current outlinee, then create a new section and append it to the outline of the current outlinee element, so that this new section is the new last section of that outline. Let current section be that new section. Let the element being entered be the new heading for the current section.

    Otherwise, run these substeps:

    1. Let candidate section be current section.

    2. If the element being entered has a rank lower than the rank of the heading of the candidate section, then create a new section, and append it to candidate section. (This does not change which section is the last section in the outline.) Let current section be this new section. Let the element being entered be the new heading for the current section. Abort these substeps.

    3. Let new candidate section be the section that contains candidate section in the outline of current outlinee.

    4. Let candidate section be new candidate section.

    5. Return to step 2.

    Push the element being entered onto the stack. (This causes the algorithm to skip any descendants of the element.)

    Recall that h1 has the highest rank, and h6 has the lowest rank.

    Otherwise

    Do nothing.

    In addition, whenever you exit a node, after doing the steps above, if the node is not associated with a section yet, associate the node with the section current section.

  5. Associate all nodes with the heading of the section with which they are associated, if any.

The tree of sections created by the algorithm above, or a proper subset thereof, must be used when generating document outlines, for example when generating tables of contents.

The outline created for the body element of a Document is the outline of the entire document.

When creating an interactive table of contents, entries should jump the user to the relevant sectioning content element, if the section was created for a real element in the original document, or to the relevant heading content element, if the section in the tree was generated for a heading in the above process.

Selecting the first section of the document therefore always takes the user to the top of the document, regardless of where the first heading in the body is to be found.

The outline depth of a heading content element associated with a section section is the number of sections that are ancestors of section in the outline that section finds itself in when the outlines of its Document's elements are created, plus 1. The outline depth of a heading content element not associated with a section is 1.

User agents should provide default headings for sections that do not have explicit section headings.

Consider the following snippet:

<body>
 <nav>
  <p><a href="/">Home</a></p>
 </nav>
 <p>Hello world.</p>
 <aside>
  <p>My cat is cute.</p>
 </aside>
</body>

Although it contains no headings, this snippet has three sections: a document (the body) with two subsections (a nav and an aside). A user agent could present the outline as follows:

  1. Untitled document
    1. Navigation
    2. Sidebar

These default headings ("Untitled document", "Navigation", "Sidebar") are not specified by this specification, and might vary with the user's language, the page's language, the user's preferences, the user agent implementor's preferences, etc.

The following JavaScript function shows how the tree walk could be implemented. The root argument is the root of the tree to walk (either a sectioning content element or a sectioning root element), and the enter and exit arguments are callbacks that are called with the nodes as they are entered and exited. [ECMA262]

function (root, enter, exit) {
  var node = root;
  start: while (node) {
    enter(node);
    if (node.firstChild) {
      node = node.firstChild;
      continue start;
    }
    while (node) {
      exit(node);
      if (node == root) {
        node = null;
      } else if (node.nextSibling) {
        node = node.nextSibling;
        continue start;
      } else {
        node = node.parentNode;
      }
    }
  }
}