嵌套有序列表必须严格写在<li>标签内,否则会导致编号重置、语义丢失和辅助技术失效;正确结构为<ol>→<li>→<ol>,子列表需闭合于同一<li>中,且多级编号需配合CSS counters()实现层级拼接。

嵌套必须写在 <li> 里面,不能悬空
浏览器只认“<ol> → <li> → <ol>”这种结构。把子 <ol> 直接写在外层 <ol> 的末尾、两个 <li> 之间,或者塞进 <p> 里,都会导致编号重置、语义丢失、屏幕阅读器跳过子项。
常见错误现象:
- 外层是 34.、35.,子列表却从 A. 重新开始(不是 34.a.)
- 子列表项缩进异常,甚至贴到左边距
- 用 Chrome 开发者工具检查时,子
<ol>被自动移到了外层<ol>外部
正确写法只有一种:<li> 内容结束前,直接写 <ol type="a">,且整个子列表必须闭合在同一个 <li> 里:
<ol start="34">
<li>First main item</li>
<li>Second main item
<ol type="a">
<li>Sub-item a</li>
<li>Sub-item b</li>
</ol>
</li>
<li>Third main item</li>
</ol>
start 属性只对外层 <ol> 生效,子列表不继承
start="34" 只会让第一个 <li> 显示为 “34.”,后续自动递增为 35.、36.……但它不会让子列表变成 “34.a.” 或 “34.1.”。子列表的起始值由它自己的 type 和是否带 start 决定——而默认就是从 1 或 A 开始。
立即学习“前端免费学习笔记(深入)”;
如果你要的是 2.1、2.2 这种带父级前缀的编号,start 就不够用了,必须用 CSS counters():
-
counters(list-item, ".")会自动拼接所有嵌套层级的list-item计数器值 - 前提是外层
<ol>有start,且子<ol>真正嵌套在<li>内 - 别手动
counter-reset—— 浏览器内置的list-item计数器已足够
简短示例(CSS):
ol ol li::before {
content: counters(list-item, ".") " ";
}
多级混合编号(A → 1 → a)靠 type 属性逐层控制
HTML 原生支持五种 type 值:1(数字)、a(小写字母)、A(大写字母)、i(小写罗马)、I(大写罗马)。每层 <ol> 单独设置,互不影响。
关键点:
-
<ol type="A">下的子<ol>必须写在某个<li>里,才能被识别为第二层 - 不要指望第一层设了
type="A",第二层不设type就自动变成数字——它默认还是1,但容易被误认为没生效 - 若某层需从非首项开始(如第二层从 2 开始),加
start="2",但仅限该层生效
结构示意:
<ol type="A">
<li>Section A
<ol type="1">
<li>Point 1</li>
<li start="2">Point 2</li>
<li>Point 3</li>
</ol>
</li>
</ol>
样式和语义冲突时,优先保语义
有人为了“让编号对齐”或“去掉默认缩进”,给 <ol> 加 list-style-type: none,再用 ::before 拼数字。这本身没问题,但一旦漏掉某层的 counter-increment 或写错 counters() 分隔符,整个编号链就断了,且辅助技术无法还原顺序逻辑。
更稳妥的做法:
- 保留原生
<ol>结构和type/start - 用 CSS 控制外观:比如
ol { margin-left: 0; padding-left: 1.5em; }调整缩进 - 真要自定义前缀(如 “Step 1.”),用
counter()替换content,但别动计数器源头 - 嵌套深了(>3 层)时,
counters()比手动管理多个counter-reset更可靠
最易被忽略的一点:子列表是否真的包裹在 <li> 里,不是看代码缩进,而是看 DOM 树结构——打开开发者工具,确认子 <ol> 是某个 <li> 的 direct child。



















