列表是页面中出现频率很高的结构,导航菜单、文章目录、商品分类都会用到<ul>或<ol>。一个很常见的排版需求是:第一项保持主题色作为当前项或标题,其余项统一使用更浅的灰色来弱化视觉权重。如果给每一个非首项单独加class,不仅增加HTML维护量,后续调整顺序还得改类名。借助CSS伪类可以让样式描述变得简洁:直接使用li:not(:first-child)配合color属性,就能一次性命中所有非首项并修改文字颜色。这个方案不依赖额外标记,也不需要脚本参与。

下面从选择器原理、颜色设置方式以及常见误区几个方面把这个技巧拆开讲清楚。
为什么用:not(:first-child)而不是额外类名
传统做法通常是在列表项上统一设置一种颜色,再给第一个<li>添加类似active或first的class,用更高优先级的规则把颜色改回来。例如:
ul li {
color: #888;
}
ul li.first {
color: #333;
}
这种方式表面上能解决,但需要额外维护HTML中的class。一旦列表项顺序变化,比如把第三个<li>移到第一位,class也要跟着改,否则第一项的颜色就不再是预设的强调色。动态渲染列表时,模板还要额外判断索引,增加条件逻辑。
使用li:not(:first-child)之后,规则直接描述为“不是第一个子元素的li”,无论列表怎么排序,浏览器都会自动根据DOM位置计算匹配结果。HTML保持干净,样式与结构解耦更彻底。对静态手写列表和由前端框架动态生成的列表都适用。
这种写法的可读性也更好。其他人阅读样式表时,能从选择器直接看出意图:排除首项,控制剩下项的颜色。相比.first这种命名,伪类表达的是通用规则,不依赖具体业务名词,迁移到不同项目更容易。
:not()与:first-child的组合原理
:first-child伪类匹配的是父元素下的第一个子元素。以<ul>为例,li:first-child可以选中列表中的第一项,前提是第一个子节点确实是li。如果第一个子节点是注释节点或文本节点,CSS中文本节点不算元素,所以没有问题;但如果第一个子元素是其他标签,比如<div>,那么li:first-child就不会命中任何li。
:not()则用来排除符合括号内选择器的元素。因此li:not(:first-child)的含义是:所有li元素中,把第一个子元素li排除,剩下的li全部选中。这个组合不涉及复杂的优先级或层级关系,但它要求列表项的父级就是承载这些li的容器。如果li外面还包了一层<div>,则需要根据实际结构改写选择器。
下面是一段完整的HTML和CSS示例:
<ul class="menu"> <li>首页</li> <li>产品中心</li> <li>服务支持</li> <li>关于我们</li> </ul>
.menu li {
color: #333;
font-size: 16px;
}
.menu li:not(:first-child) {
color: #999;
}
在这个示例中,第一个li的文字保持#333深色,后面的产品中心、服务支持、关于我们都会变成#999。这里基础规则其实可以省略,因为非首项会被覆盖,但保留基础颜色可以让规则更明确,也方便后续调整。如果想让首项和其他项有细微差别,只把非首项设置成目标颜色即可。
用color设置颜色的多种方式
文字颜色由color属性控制,可以使用十六进制、rgb/rgba、hsl/hsla以及CSS变量。对于非首项,常用做法是保留首项的品牌色或高对比色,将非首项设置为灰色系来降低视觉优先级。
.list li {
color: #1a1a1a;
}
.list li:not(:first-child) {
color: rgba(26, 26, 26, 0.55);
}
使用rgba的好处是可以基于同一种色相调整透明度,避免灰阶颜色在不同背景下显得突兀。比如首项是接近黑色的主文字,非首项用同样黑色的55%不透明度,看起来像自然的次要文字。如果首项是主题蓝色,非首项可以设置为rgba(30, 80, 200, 0.6),保留品牌色但弱化存在感。
也可以借助CSS变量统一管理颜色,让列表在不同主题下更容易切换:
:root {
--text-primary: #222;
--text-secondary: #888;
}
.list li {
color: var(--text-primary);
}
.list li:not(:first-child) {
color: var(--text-secondary);
}
这种方式把颜色值集中定义,修改主题时不需要逐个查找选择器。注意color具有继承性,如果li内部还有<a>标签,文字颜色需要直接设置在a上,否则用户代理样式可能覆盖继承值。例如:
.list li:not(:first-child) a {
color: inherit;
}
当列表项是链接时,建议把li和a的颜色策略一起考虑。可以先让a继承li的颜色,再对非首项li应用弱化颜色,这样所有后代文字都能保持统一。
与:first-of-type的区别及常见误区
:first-child和:first-of-type经常被混用,但两者语义并不相同。:first-child要求元素必须是父容器的第一个子元素;:first-of-type匹配的是同级元素中该类型的第一个,即使前面有其他类型的兄弟元素也能命中。
例如下面结构:
<div class="wrap">
<h3>标题</h3>
<ul>
<li>第一项</li>
<li>第二项</li>
</ul>
</div>
如果选择器写成.wrap li:first-child,第一项li是ul的第一个子元素,可以命中;但如果ul前面还有一个<h3>,对li来说父级是ul,h3不在同一父级内,所以不影响。真正容易出错的场景是li和其他元素同属于一个父容器,例如用<div>做分隔,此时li:first-child可能一个都选不中,而li:first-of-type仍能选中第一个li。
另一个误区是认为:not(:first-child)可以作用于任意列表,但如果第一个子元素不是li,比如存在一个<span>作为首个子节点,那么li:not(:first-child)会选中所有li,包括视觉上的第一项。遇到这种情况,可以改用li:not(:first-of-type),或者把分隔元素放到ul外部,保证ul的直接子元素都是li。
动态列表中还要注意:如果首项被隐藏或移除,原本的第二项会自动变成第一项,颜色会立即恢复为首项颜色。这通常是符合预期的,但如果业务逻辑希望固定某一项作为强调项,就需要额外类名或状态标记来处理,而不是单纯依赖位置选择器。
工程实践中的几个建议
在真实项目中,不要只写li:not(:first-child),最好加上父容器作用域,避免影响页面里所有列表。比如使用.nav-list li:not(:first-child),或者BEM风格的.menu__item:not(:first-child),这样在不同模块中可以独立控制颜色。
如果列表项点击后需要切换当前项样式,纯CSS的:first-child就不再够用,此时需要引入.active类并配合:not(.active)来动态排除当前项。这种模式与本文思路一致,只是把位置条件换成了状态条件。脚本只需维护一个类名,其余交给CSS处理。
关于兼容性,:not()选择器在现代浏览器中支持良好,但旧版IE对复杂选择器的支持有限。如果项目需要兼容IE9以下,建议做好降级:先给所有项设置基础颜色,再用li:not(:first-child)覆盖,旧浏览器即使不识别该选择器,也只是颜色不区分,不影响可读性。不要依赖这个选择器完成关键布局或核心交互。
最后,颜色选择要考虑可读性。非首项文字颜色与背景需要满足对比度要求,不建议为了弱化层次而把颜色调得过浅。通常正文使用#666到#999之间的灰度,具体值根据背景亮度调整。