counter-increment 必须作用于真实 DOM 元素,写在伪元素上会被浏览器静默忽略;正确做法是将 counter-increment 设在 li 等真实元素上,::before 仅负责 content: counter() 显示。

counter-increment 写在伪元素上根本不会触发
伪元素(::before 或 ::after)本身不参与计数器递增流程,counter-increment 必须作用于真实 DOM 元素。写在伪元素选择器里,浏览器会静默忽略——不报错、不警告、不累加。
常见错误写法:li::before { counter-increment: item; content: counter(item); } → item 永远是 0。
- 正确做法:把
counter-increment移到li本体上,::before只负责显示 - 如果用 JS 动态插入新
li,需确保它也匹配该规则,否则新增项不计入 - 避免在
display: none的元素上设counter-increment,它会被完全跳过
counter-reset 位置不对导致计数重置失败
counter-reset 必须声明在真实父容器上,且要能“覆盖”所有需要计数的后代元素。写在伪元素、display: contents 容器或 Shadow DOM 内部,都会中断作用域。
典型失效场景:.list::before { counter-reset: step; } → 对 .list > li 完全无效。
立即学习“前端免费学习笔记(深入)”;
- 可靠写法:
.list { counter-reset: step; }+.list li { counter-increment: step; } - Flex/Grid 容器中,计数仍按 DOM 顺序,不是视觉顺序(
order不影响) - 嵌套计数器需用不同名称,如
counter-reset: section subsection;,避免冲突
content 中用了 counter() 但没配对初始化
counter() 函数只读取已存在的计数器值。如果 counter-reset 没生效,或被更高优先级规则覆盖,content: counter(x) 就会返回空或 0。
检查点:Computed 面板里搜 counter,看是否出现;或临时加 counter-reset: x 99; 测试是否可读。
- 多个同名
counter-reset会以最后一条为准,注意 CSS 加载顺序和 specificity -
counter-reset: x var(--start, 0);中的var()只在counter-reset里合法,不能用于content - 若父容器有
contain: layout,可能隔离计数器上下文,需移除验证
伪元素本身没生成,content 根本没机会执行
即使 content: counter(x) 写对了,只要伪元素盒模型没创建,整个逻辑就卡在第一步。而伪元素创建的前提只有一个:content 值必须是非 none 的有效值。
错误示例:div::after { content: counter(x); } → 若 x 未被 counter-reset 初始化,content 计算为 none,伪元素直接消失。
- 调试技巧:先写死
content: "test";确认伪元素能否显示,再换回counter() -
content: counter(x) " / " counter(y);中任一计数器失效,整条content都会退化为none - 不要依赖
attr()和counter()混合输出,它们无法拼接
counter() 本身的问题,而是 DOM 结构变动后,counter-increment 触发时机与预期不符,比如动态渲染时漏掉了某一层元素的匹配。


















