选好字体,只是文字排版的起点。字形怎样组成一行、在哪里换行、遇到图片时如何占位、重点内容如何标记,还要由一组文本属性共同决定。我们在内容页里遇到的许多问题,也藏在这些细节中:正文行高能否随字号变化,链接是否容易辨认,长网址会不会撑破卡片,中文标点又会在哪里断开。
下面先从浏览器使用的排版坐标系讲起,再依次看缩进、对齐、间距、行盒、装饰、阴影、空白、断词和纵排。你可以边读边运行示例,看看每个属性改变的究竟是哪一层排版结果。
学习文本属性时,先问“它控制的是整块内容、行盒,还是某个行内盒”,再问“它是否继承”。这两个问题可以解释绝大多数看似反常的排版结果。
我们平时会说文字是“横排”或“竖排”,但遇到阿拉伯文、希伯来文或竖排中文,这两个词很难把方向说清楚。CSS 因此用两条轴来描述文字流动:
vertical-rl 竖排则从右向左增加新列。start、end 这样的逻辑值会跟着行内方向变化,left、right 则始终指向固定的物理边。如果页面要容纳不同语言,优先使用逻辑方向,同一套样式就能随书写模式调整位置。

.article-title {
text-align: start;
}
.article-meta {
margin-inline-start: auto;
}这里要分清旋转和竖排。transform: rotate(90deg) 只是把已经排好的横向内容整体转过去;writing-mode 才会重新确定行内轴与块轴,同时改变换行方式、标点朝向和行盒构造。
text-indent 用来移动块盒中的第一行,移动方向沿着行内方向,所以它不一定是向右。这个属性会继承,初始值是 0。中文正文常见的 2em 会随当前字号变化,比固定像素更容易保持两个字宽的缩进。
.prose p {
text-indent: 2em;
}百分比以包含块的行内尺寸为参照,并不随字号计算;在普通横排中,它通常就是包含块宽度。负值可以做出悬挂缩进,不过第一行可能伸到容器外被裁掉。使用时需要在逻辑起始侧留出内边距。
.reference-item {
padding-inline-start: 3em;
text-indent: -3em;
}实验性的 hanging 会缩进第一行之外的各行,each-line 还会让强制换行后的新行获得缩进。它们的浏览器支持需要按项目目标检查。要兼顾稳定性,通常还是使用负缩进配合内边距。text-indent 不直接移动普通行内元素或图片,但行内图片如果出现在第一行,仍会跟着整行一起移动。
text-align 与最后一行text-align 决定块内每一行的行内对齐方式,常见值有 start、end、left、right、center 和 justify。它移动的是块里的文字等行内内容,不会移动块盒本身。固定宽度的卡片要居中,仍应交给外边距、Grid 或 Flex。
.quote {
text-align: center;
}
.news-column {
text-align: justify;
text-align-last: start;
}使用两端对齐后,浏览器会拉开可用的词间距,有时也会调整字符间距,让最后一行之外的各行同时贴住两侧。栏宽较窄时,这些被拉大的空隙会连成明显的“空白河流”;中文也可能出现不均匀的字距。因此,正文是否两端对齐要结合栏宽和实际文字判断。
text-align-last 管的是元素最后一行,也包括紧挨着 <br /> 等强制换行之前的那一行。单行段落既是第一行也是最后一行,因此视觉结果会优先采用 text-align-last。如果希望最后一行也两端对齐,兼容性较稳的写法是同时设置 text-align: justify 和 text-align-last: justify。justify-all 表达的是同一意图。另一个较少用到的值 match-parent,会先按父级方向解析父元素的 start 或 end,再把结果用于子元素。
word-spacing 调整浏览器认定的词间空白,letter-spacing 调整字符之间的距离。两者都接受正负长度,也都会继承。normal 和 0 平时看起来相近,含义却有差别:两端对齐时,浏览器仍可继续调整 normal 的字符间距;显式写成 letter-spacing: 0,则表示不要为对齐再改变这段间距。
.eyebrow {
letter-spacing: 0.12em;
}
.english-lead {
word-spacing: 0.08em;
}浏览器怎样识别“词”,取决于语言和空白规则。连续中文通常没有空格,所以 word-spacing 对中文正文几乎看不出效果。字距改得过大或过小,还可能破坏连字与字形连接。
继承时还有一个容易漏掉的细节。父元素上的 0.25em 会先按父级字号计算为长度,子元素继承的是这个计算结果。即使子元素字号减半,间距也不会自动减半。想让间距继续保持相同比例,需要在字号变化的子元素上重新声明。
你可以在下面同时调整首行缩进、整段对齐、最后一行对齐和字词间距。改变容器宽度后,两端对齐与最后一行的差异会更容易观察。
line-height 与行盒的构造font-size 决定字形内容区的大致尺寸,line-height 决定连续基线之间的最小距离。用后者减去前者,就得到总行距:
总行距 = 计算后的 line-height − 计算后的 font-size
半行距 = 总行距 ÷ 2浏览器把总行距平分到内容区上下两侧,由此构成行内盒。最终的行盒还要容纳这一行里的其他行内盒、图片和控件:最高的顶部与最低的底部都必须装得下。这样看就容易理解,块元素上的行高只是基线距离的下限;行内放入更高的图片后,实际行盒仍然会被撑大。

line-height 可以写成 normal、长度、百分比或无单位数值。normal 由字体和浏览器决定,常见结果约为字号的 1.2 倍,但这不是固定公式。正文可先从 1.5 到 1.8 的无单位值试起,再根据字号、字面大小、行长与语言调整。
body {
line-height: 1.65;
}
.compact-label {
line-height: 1.2;
}无单位值的好处在继承时最明显。假设父级字号为 10px,并设置 line-height: 1em,浏览器会先算出 10px。子元素即使把字号改为 18px,仍可能继承这 10 像素,文字就容易重叠。父级若改写为 line-height: 1.5,子元素继承的是倍率,会按自己的字号算出 27px。
不要用固定高度加 flex 居中来替代正文行高。那只能摆放整个文本盒,不能解决多行文字内部的基线距离、行内图片和上标对行盒的影响。
vertical-align 对齐的是行内盒vertical-align 主要用于行内元素、::first-letter、::first-line 和表格单元格。普通块盒里的多行正文不会因为它而垂直居中。表格单元格能够识别的值也较少,主要是 baseline、top、middle 和 bottom 等。
baseline:元素基线与父级基线对齐;图片没有基线,会用底边对齐基线,因此下方常留出一小段空隙。可按需求改为 middle、bottom,也可以把图片设为块级。sub / super:下移或上移基线,但不会自动缩小字号。真正的上下标往往还要配合较小 font-size。top / bottom:与整条行盒的顶部或底部对齐。text-top / text-bottom:与父元素字体形成的文本区域顶部或底部对齐,忽略同一行里更高的图片。middle:元素中点对齐到父级基线上方约 0.5ex 的位置,并不是容器的几何中心。line-height。移动后的盒子仍参与行盒高度计算,可能把整行撑高。.formula sup {
font-size: 0.75em;
vertical-align: 0.45em;
}
.icon-with-text {
vertical-align: -0.125em;
}text-transform 只改变显示出来的大小写,不会改写源文本。uppercase、lowercase 和 capitalize 分别显示为全大写、全小写和每个词的首字母大写,none 保持原样。capitalize 只识别词首,不理解标题的编辑规则,也不会自动判断冠词是否该小写。词边界与大小写映射都依赖语言,所以页面还要正确设置 lang。
full-width 会把可用字符映射为全角形式,便于和方形表意文字网格协调;full-size-kana 会把小假名转换为完整尺寸,多用于注音场景。使用这些值之前,要检查目标浏览器的实际支持情况。
.product-code {
text-transform: uppercase;
}
:lang(en) .section-kicker {
text-transform: capitalize;
}全大写会改变单词轮廓,段落一长就更难扫读。部分辅助技术还可能把全大写内容当成缩写逐字朗读,所以这种样式更适合短标签,不宜用于关键长文。
text-rendering 最初用于表达矢量文本的渲染倾向,常见值有 auto、optimizeSpeed、optimizeLegibility 和 geometricPrecision。它不能保证各平台得到相同结果,连字、字偶距和提示信息仍由操作系统、字体与浏览器共同决定。现代设备上,手动设置这个属性通常变化不大,是否使用要以真实设备测试为准。
文本装饰由几项可以独立控制的属性组成:
a:any-link {
text-decoration-line: underline;
text-decoration-color: currentColor;
text-decoration-style: solid;
text-decoration-thickness: from-font;
text-underline-offset: 0.14em;
text-decoration-skip-ink: auto;
}text-decoration-line 可以组合 underline、overline 和 line-through,已经废弃的 blink 不应再用。text-decoration-color 默认取 currentColor。text-decoration-thickness 可以写固定长度、字号百分比、auto 或 from-font,其中相对单位会随着字号缩放。text-decoration-style 支持 solid、double、dotted、dashed 和 wavy。
简写 text-decoration 可以同时写线型、样式、颜色和粗细:
.spell-error {
text-decoration: wavy #d33 underline 0.1em;
}使用简写时,没写出的子属性会被重置,新的简写也会替换旧装饰,不会自动叠加。同一元素上的多条装饰线共用颜色和样式。下划线与上划线如果要用不同颜色,需要用嵌套元素分别绘制。
text-underline-offset 只调整下划线的位置。正值会让它沿块方向远离文字,负值则让它靠近。text-decoration-skip-ink: auto 允许浏览器在下划线经过字形下降部时暂时断开,none 强制连续绘制,all 要求跳过字形,但需要留意兼容性。
装饰属性本身不会继承,父元素画出的线却会贯穿后代。对子元素设置 text-decoration: none,擦不掉父元素的线,因为这条线原本就不是子元素绘制的。用背景色画一条同位置、同粗细的装饰线可以遮住它,但这种处理很容易随背景或字号变化而失效。更稳妥的做法是调整 DOM,让装饰落在真正需要它的元素上。
正文链接去掉下划线后若只靠颜色区分,会让色觉障碍用户和快速扫读者难以发现链接。交互状态至少要保留形状、粗细或其他非颜色线索。
一层 text-shadow 包括横向偏移、纵向偏移、可选的模糊半径和颜色,多层阴影之间用逗号分隔。省略颜色时通常会取 currentColor。阴影只影响绘制,不参与盒模型,也不会增加行高,所以它可以超出元素的视觉边界。
.hero-title {
color: white;
text-shadow:
0 1px 2px rgb(0 0 0 / 55%),
0 0 10px rgb(0 0 0 / 30%);
}浅色文字放在明暗变化较大的背景上时,一层范围较小的阴影可以补足边缘对比。阴影层数太多、模糊半径过大,或让阴影持续动画,都会增加绘制成本,尤其要在移动设备上测试。
中文排版还可以用 text-emphasis 在可标记字符旁放置着重号。text-emphasis-style 支持实心或空心的点、圆、双圆、三角与芝麻点,也可以传入单字符字符串;多字符字符串可能只采用首字符。text-emphasis-color 默认跟随文字颜色。text-emphasis-position 在横排中控制 over / under,在竖排中控制 left / right。样式和颜色可以写进 text-emphasis 简写,位置没有放进简写,这样修改样式时不会顺手重置排版位置。
.key-sentence {
text-emphasis: filled sesame #c83b31;
text-emphasis-position: under right;
}着重号会继承。为了避开相邻行,它还可能增加行高;空格、标点和控制符通常不会带着重号。浏览器大体按阴影、下划线、上划线、文字、着重号、删除线的顺序从后向前绘制。几个效果重叠时,可以用这个顺序判断谁会盖住谁。

HTML 默认会折叠连续空格,也会忽略源码中的换行。white-space 把三个决定组合在一个属性中:空白是否折叠,源码换行是否保留,内容是否允许自动换行。
normal:折叠空白,忽略源码换行,允许自动换行。nowrap:折叠空白,但禁止普通软换行;<br /> 仍可强制换行。pre:保留空白与源码换行,禁止自动换行。pre-wrap:保留空白与源码换行,同时允许自动换行;行尾空格可以悬挂在行尾。pre-line:折叠连续空白,保留源码换行,并允许自动换行。break-spaces:像 pre-wrap 一样保留空白,但每个保留的空格后都可断行,行尾空格也占据尺寸并影响 min-content / max-content。.terminal-output {
white-space: pre-wrap;
}
.user-note {
white-space: break-spaces;
}只有制表符没有被空白规则折叠时,tab-size 才会显示出效果。整数表示一个 Tab 相当于多少个空格,初始值通常为 8;长度值则直接指定制表宽度。这个属性会继承,负值无效。
pre {
tab-size: 4;
}一行文字在哪里断开,是几组规则依次判断的结果。white-space 先决定能否软换行;语言、word-break 和 line-break 再提供正常断点;如果一长串内容仍然放不下,overflow-wrap 才会介入。hyphens 负责决定断词处是否显示连字符。

hyphens 的初始策略是 manual,只响应软连字符 U+00AD 等人工提示;none 会连人工提示也忽略;auto 则允许浏览器依据语言词典断词。自动断词依赖 lang、词典和浏览器,各平台的断点不一定相同。<wbr /> 只提供断行机会,不会在行尾显示连字符;软连字符只有真正发生断行时才会出现。
<p lang="en" class="essay">Antidisestablishmentarianism...</p>.essay {
hyphens: auto;
}
code,
kbd,
textarea,
input {
hyphens: none;
}如果在 body 上开启自动断词,记得为代码、变量、命令和输入框关闭它。hyphens 会继承,而命令中多出的一个连字符,就可能让复制后的内容无法运行。
word-break 用来确定普通软断点:
normal 按语言规则断行。break-all 几乎可在任意字符间断开,哪怕把英文单词切碎,并且不会自动显示连字符。keep-all 抑制中日韩文本在字符间断开,连续文本可能溢出。break-word 已废弃,其历史效果近似 word-break: normal; overflow-wrap: anywhere。line-break 进一步控制中日韩字符与标点附近的断行。loose 规则较宽松,适合窄栏;normal 使用常见规则;strict 更严格;anywhere 会在每个排版单元附近提供断行机会,并且不做连字。标点的具体表现仍可能随浏览器和语言而变化。
overflow-wrap 会等到不可断字符串确实要溢出时再介入:
normal 不额外制造断点。break-word 必要时在词内断开,但这些临时断点不参与 min-content 内在宽度计算。anywhere 同样可在词内断开,并把这些断点纳入 min-content 计算,因此网格或弹性布局里的卡片更容易真正缩小。word-wrap 是 overflow-wrap 的历史别名。相比 word-break: break-all,overflow-wrap 会先使用正常的空白断点,只有内容确实放不下时才切开长词,因此更适合需要保留自然阅读节奏的正文。
用户生成内容卡片的稳健起点通常是 overflow-wrap: anywhere;自然语言长文则应先设置正确的 lang,再评估 hyphens: auto。代码内容应优先水平滚动,避免把标识符切得无法复制。
writing-mode 会改变块流方向:
horizontal-tb:行内方向水平,新行从上到下排列。vertical-rl:文字沿竖直行内轴前进,新列从右向左排列,传统中文竖排常用。vertical-lr:文字竖直前进,新列从左向右排列。sideways-rl / sideways-lr:把横排文字整体侧转并按相应块方向推进,需检查目标浏览器支持。.vertical-caption {
writing-mode: vertical-rl;
text-orientation: mixed;
}切换到竖排后,vertical-align 仍然相对行盒移动行内元素,只是视觉上可能表现为水平位移,不能再按属性名直觉理解。竖排行内字符的朝向由 text-orientation 决定:mixed 通常让表意文字直立、拉丁文字侧转;upright 让所有字符直立;sideways 让所有字符侧转。
text-combine-upright 可以把竖排中的短数字压进一个全角字符的空间。处理日期时,可以把两到三位数字单独放进 <span>,再设置 all,这样比作用于整段更可控。对整段使用会把过多字符挤在一起。digits 2、digits 3、digits 4 表达得更精确,不过上线前仍要在目标环境中验证。
<p class="date-vertical">七月<span class="combine">13</span>日</p>.date-vertical {
writing-mode: vertical-rl;
}
.combine {
text-combine-upright: all;
}从右到左或混合方向的内容,优先在 HTML 中写清语义:使用 dir="rtl"、dir="ltr"、dir="auto",需要强制覆盖时再用 <bdo>。CSS 的 direction 会影响文本方向、表格列顺序、横向溢出方向以及两端对齐的最后一行。行内元素还要结合 unicode-bidi: embed 或 bidi-override,才会以特定方式改变双向算法。unicode-bidi: normal 不会新增嵌入层,embed 会新建方向嵌入,bidi-override 则忽略隐式双向重排,使用风险最高。
文本方向属于内容语义。若样式表加载失败,HTML 的 dir 仍能保护阅读顺序;只用 CSS direction 和 unicode-bidi,可能让样式失效时的文本含义发生变化。
属性值通常查一下就能确认,真正容易出错的是把属性用在了错误的盒子或场景里。上线前可以按下面这份清单逐项检查:
text-align: center 居中卡片。 它只居中卡片里的行内内容;卡片本身要用 Grid、Flex 或逻辑外边距。vertical-align: middle 当块级垂直居中。 它对普通块布局不起这个作用,且行内 middle 也不是几何中心。word-break: break-all。 它会在已有更好断点时仍切碎英文;用户内容优先考虑 overflow-wrap: anywhere。white-space: nowrap 做省略号却漏掉约束。 单行省略通常还要 overflow: hidden、text-overflow: ellipsis 和可收缩的宽度;在 Flex 子项里常需 min-width: 0。none 无法撤销。lang。 浏览器没有语言线索,就无法选择正确词典与标点规则。writing-mode。实际写样式时,可以沿着一条清楚的路径排查。先确认语言和书写方向,再处理块内对齐与缩进;随后用无单位行高建立稳定的行盒距离。需要强调内容时,先保证链接和重点容易辨认。面对用户输入,再分别考虑空白、正常断点、应急断点与连字符。样式写完后,还要放进真实字体、窄屏和多语言内容中检查。
.readable-prose {
line-height: 1.65;
text-align: start;
overflow-wrap: anywhere;
}
.readable-prose:lang(en) {
hyphens: auto;
}
.readable-prose a:any-link {
text-decoration: underline currentColor from-font;
text-underline-offset: 0.14em;
text-decoration-skip-ink: auto;
}不存在适用于所有场景的文本属性组合。我们需要回到内容本身做判断:行高要配合字号与行长,断行规则要保护容器,逻辑方向要跟随语言,链接装饰则要提供颜色之外的识别线索。HTML 中的语言与方向语义也应写完整,这样即使样式加载失败,内容顺序仍然正确。