gsd 的 userinterface-wiki 规则解读:用原生 `::first-line` 实现首行排版样式,告别硬编码 span

原创2026-10-08 23:44:511,999 阅读
文章标签:人工智能AI Agent代码智能体CLIMCP 服务前端

gsd 的 userinterface-wiki 规则解读:用原生 ::first-line 实现首行排版样式,告别硬编码 span

本篇技术指南聚焦 gsd 项目内置的 UI/UX 技能包 userinterface-wiki 中编号为 pseudo-first-line-styling 的 CSS 伪元素规则。该规则解决的是一个非常具体的排版场景:如何在不引入 JavaScript、不硬编码 <span> 的前提下,让段落首行呈现小号大写(small-caps)、加粗等与正文不同的排版效果。读完本文,你将掌握 ::first-line 的正确用法与适用边界、它与其他伪元素规则(::before/::after、::marker)的分工,以及如何把这条规则放进 gsd 的技能规则体系中参与自动化代码评审。

规则原文:核心主张

规则文件 pseudo-first-line-styling.md 的元信息如下:

字段 值
title Use ::first-line for Typographic Treatments
impact LOW
tags pseudo, first-line, typography

规则主张一句话即可概括:

使用 ::first-line 实现首行排版(drop-cap-adjacent styling),无需 JavaScript,也无需硬编码 span。

这里的 "drop-cap-adjacent styling" 指的是围绕首字下沉(drop cap)周边的排版修饰:真正的下沉首字由 ::first-letter 完成,而与下沉首字相邻的首行文字(例如整段首行统一采用小型大写字母或加粗)则交给 ::first-line。两条规则各司其职,共同构成完整的段落首行排版方案。

反面模式:硬编码 span 的代价

规则给出了明确的反例——在 TSX 中手动包一层 <span> 来圈定"首行":

<p>
  <span className={styles["first-line"]}>The opening line of this paragraph</span>
  is styled differently from the rest.
</p>

这种写法存在三个隐患:

  1. 首行不可预测:排版中的"第一行"取决于视口宽度、字号、字间距等因素,手动用 span 圈定的文字在窄屏或大字号下会溢出到第二行,样式错位;
  2. 污染 DOM:为纯装饰目的增加额外的 DOM 节点,增加渲染成本与 CSS 选择器匹配负担,这正与同一技能包中 pseudo-over-dom-node("装饰性内容优先使用伪元素而非多余 DOM 节点")的规则相抵触;
  3. 破坏语义:<span> 承载的是行内语义分组,而非排版状态,将其当作样式工具使用会混淆结构层与表现层的职责。

正确模式:原生 ::first-line

规则给出的正确示例使用 CSS 原生伪元素,无需改动 JSX 结构:

.article p:first-of-type::first-line {
  font-variant-caps: small-caps;
  font-weight: var(--font-weight-medium);
}

拆解这条选择器与声明的用意:

  • .article p:first-of-type:限定只作用于文章正文里的第一个段落,避免全站每个段落都套用首行样式;
  • ::first-line:由浏览器根据当前格式化后的实际换行位置自动确定首行,任何断行变化(窗口缩放、字体加载)都会自动重新计算,彻底摆脱手动圈定;
  • font-variant-caps: small-caps:将首行文字渲染为小型大写字母,这是首行排版最常见的处理手法之一;
  • font-weight: var(--font-weight-medium):复用设计系统的 CSS 自定义属性(design token)表达中等字重,保证与全局字重规范一致——这与该技能包中"设计 token 贯穿 UI 规则"的思路保持统一。

JSX 一侧保持最朴素的段落结构即可,样式完全下沉到 CSS:

<p>The opening line of this paragraph is styled by CSS alone.</p>

原理纵深:::first-line 的能力与边界

从 CSS 规范角度看,::first-line 用于选中块级容器内格式化后的第一行,理解它需要注意以下边界:

  • 仅适用于块级容器:::first-line 只对块级盒(block container)生效,display: inline 的元素无法使用;
  • 可设置的属性受限:规范允许在该伪元素上生效的属性是一组与排版强相关的子集(如 font 族、color、background 族、text-decoration、text-transform、letter-spacing、word-spacing、line-height、font-variant 等),并不支持所有 CSS 属性;
  • 与 ::first-letter 协作:::first-letter 负责真正的首字下沉,::first-line 负责下沉字所在的那一行——二者可以同时作用于同一段落,互不冲突;
  • 自动重排:首行边界由浏览器在布局阶段确定,因此响应式断行、动态字体加载都不会造成样式错位,这是它相对 span 方案的本质优势。

技能包中的同类规则:伪元素体系的整体视角

pseudo-first-line-styling 只是 userinterface-wiki 中 CSS 伪元素大类的一条。在 SKILL.md 的 12 大类规则表中,"CSS Pseudo Elements" 被归为 MEDIUM 影响级,前缀为 pseudo-、transition-、native-,其定位在 _sections.md 中被描述为:

利用 ::before、::after、View Transitions API 以及原生伪元素(::backdrop、::placeholder、::selection)来减少 DOM 节点并改善过渡表现。

同前缀的兄弟规则共同构成了"伪元素优先"的完整方法论:

规则文件 核心主张
pseudo-content-required.md ::before/::after 必须设置 content 属性才会渲染(HIGH 影响级)
pseudo-over-dom-node.md 装饰性内容用伪元素而非多余 DOM 节点
pseudo-marker-styling.md 列表自定义符号用 ::marker,而非背景图 hack
pseudo-position-relative-parent.md 伪元素绝对定位时父级需要 position: relative
pseudo-z-index-layering.md 用 z-index 控制伪元素层级
pseudo-hit-target-expansion.md 用负 inset 扩大点击热区

可见 ::first-line 与 ::marker、::before/::after 等规则同属一条原则的延伸:能用原生伪元素解决的排版与装饰需求,就不要增加 DOM 或脚本复杂度。pseudo-first-line-styling 的 impact 标记为 LOW,说明它属于"锦上添花"级的排版细节——修正它能提升视觉完成度,但优先级低于影响可访问性或交互正确性的 HIGH 级规则。

排版搭档:小型大写字母需要 letter-spacing

规则示例中使用了 small-caps,而该技能包中另一条 Typography 类规则 type-letter-spacing-uppercase.md(MEDIUM 影响级)正好补上了配套细节:

大写与小型大写文字需要正向 letter-spacing 才显得开阔、可读。

其反例是仅仅 text-transform: uppercase; font-size: 12px; 而不加字距;正确做法是追加 letter-spacing: 0.05em;。因此在落地 ::first-line 首行方案时,建议把两条规则组合使用:

.article p:first-of-type::first-line {
  font-variant-caps: small-caps;
  font-weight: var(--font-weight-medium);
  letter-spacing: 0.05em; /* 配合 type-letter-spacing-uppercase 规则 */
}

两条规则一个解决"选中首行",一个解决"首行文字的可读性细节",在 SKILL.md 的 Typography 大类(前缀 type-)下还有 type-optical-sizing-auto、type-text-wrap-balance-headings、type-no-font-synthesis 等十余条可继续沿用的配套规范。

这套规则如何被 gsd 使用

userinterface-wiki 是 gsd 仓库中一个随包分发的技能资源:它以 SKILL.md 为入口,声明自己"涵盖从动画原理到排版的 11/12 类主题,按影响级排序,用于指导自动化代码评审与生成,输出 file:line 形式的发现"。每条规则文件(如本文主角 pseudo-first-line-styling.md)遵循 _template.md 定义的统一结构:

  • 简要说明规则及它为什么重要;
  • 反面代码示例;
  • 正确代码示例。

也就是说,当你(或 gsd 驱动的 Agent)在审查一段含首行样式处理的代码时,可以在规则集中快速比对:若代码使用了硬编码 span,即命中 pseudo-first-line-styling 反例,应改为原生 ::first-line;同时可联动检查 pseudo-content-required(若用了 ::before/::after)、type-letter-spacing-uppercase(若用了 small-caps)等关联规则,形成一站式的伪元素排版审查清单。

落地自查清单

把这条规则应用到你的项目中,可以参考以下检查点:

  1. 首行样式是否用了原生 ::first-line,而非 JS 计算行宽或硬编码 <span>;
  2. 选择器是否限定范围(如 p:first-of-type),避免全站首行都被改样式;
  3. 是否只设置 ::first-line 支持生效的排版属性(font 族、color、背景、text-transform、letter-spacing、word-spacing、line-height 等);
  4. 若涉及首字下沉,确认 ::first-letter 与 ::first-line 分工正确;
  5. 若使用了 small-caps,是否同步补充了 letter-spacing 以维持可读性;
  6. 是否保留了语义化的 HTML 结构,样式全部下沉到 CSS 层。

遵循这套规则,你得到的是一份更轻的 DOM、更可预测的响应式排版,以及一个符合 gsd 技能体系规范的代码审查依据。

登录后查看全文
gsd-2