在Web排版中,有序列表<ol>和无序列表<ul>是最基础的内容组织方式。当我们需要表达章节、子章节这类具有层级关系的信息时,往往会采用列表嵌套。然而实际编码时,很多页面出现了子级序号从1重新开始、父级序号断裂、甚至整个编号消失的情况。这些问题本质上都源于对HTML列表模型的误解,而非CSS样式丢失。

浏览器如何计算嵌套列表的序号
HTML规范将列表定义为一种具有父子继承关系的容器模型。对于有序列表<ol>,其直接子元素只能是<li>,而每一个<li>内部可以再包含新的<ol>或<ul>。浏览器在构建渲染树时,会为每个<li>维护一个计数器,该计数器的作用域局限于当前列表元素。当子列表被正确地放置在某个<li>的内部时,父<li>的计数并不会因为子列表的存在而中断,子列表则开启自己独立的计数上下文。
如果子列表没有被包裹进<li>,而是与<li>成为同级节点,那么原本属于同一个<ol>的<li>就会被多个列表元素拆分。此时浏览器认为第一个<ol>已经结束,后续出现的<ol>是一个全新列表,自然会从1开始编号,造成视觉上的序号不连续。理解这一点,就能明白为什么结构调整比修改CSS的list-style更为根本。
从语义角度讲,嵌套列表表达的是"一个条目下包含一组子条目"。这种关系在DOM中必须体现为包含关系,而不是相邻关系。HTML解析器在容错处理时,可能自动补全或移动标签,导致开发者在DevTools中看到的树结构与自己写的源码并不一致,进一步掩盖了错误写法。
典型错误写法与正确结构对比
下面是一段常见的错误代码,作者希望实现带子项的教程大纲,却把子<ol>写在了<li>外面:
<ol>
<li>第一章 基础概念</li>
<ol>
<li>什么是HTML</li>
<li>标签与元素</li>
</ol>
<li>第二章 进阶内容</li>
</ol>
在上述结构中,第二个<ol>并非第一个<li>的一部分,而是与两个<li>平级。浏览器解析后,会将"第一章"编号为1,紧接着遇到新<ol>,其子项又从1开始,随后"第二章"回到外层<ol>被编号为2。最终用户看到第一章下面挂着1、2两个子点,外层却是1、2章节,逻辑完全混乱。
正确的写法应当将子列表放入父<li>的结束标签之前:
<ol>
<li>第一章 基础概念
<ol>
<li>什么是HTML</li>
<li>标签与元素</li>
</ol>
</li>
<li>第二章 进阶内容</li>
</ol>
这样修改后,外层<ol>只有两个<li>,序号为1和2;内层<ol>位于第一个<li>内部,拥有独立的1、2编号。层级清晰且符合规范。若需要自定义起始值,可使用<ol>的start属性,或利用CSS的counter-reset与counter-increment精细控制,但前提依然是结构嵌套正确。
语义化嵌套的最佳实践与可访问性
除了修复序号,语义化写法还直接影响辅助技术的表现。屏幕阅读器在遇到正确嵌套的列表时,会提示用户"列表包含2项,其中第一项包含子列表2项",视障用户由此建立清晰心智模型。若使用<div>配合手工数字或单纯用缩进模拟层级,读屏软件只能按顺序读出文本,丢失了结构信息。
在复杂文档中,我们可以用<ul>表达无顺序的导航树,用<ol>表达有步骤依赖的流程。无论哪种,都应避免"扁平化"写法,即把所有层级铺平为单层列表再通过缩进样式伪装嵌套。如下示例用CSS让深层列表自动显示层级计数,但依然依赖正确的HTML包含关系:
ol {
list-style: none;
counter-reset: section;
}
li::before {
counter-increment: section;
content: counters(section, ".") " ";
}
这段CSS利用counters函数将父级与子级序号以"1.1"、"1.2"形式呈现,非常适合法律条款或标准文档。但它要求DOM中<li>必须层层包裹,否则counters只会取到当前扁平列表的值。结合前文的正确嵌套,我们既能获得连贯序号,又能让机器与人类都准确理解内容层级,这才是稳固且可维护的列表书写方式。
HTMLnested_listsemantic_markup修改时间:2026-08-13 20:30:27