LOADING
2098 字
10 分钟
从一个静态博客的样式混乱,聊到一套撑得住的前端样式体系

这篇文章记录的是一次真实的返工:一个已经上线、文章也写了几十篇的静态站点,因为样式越改越乱,被迫停下来重新梳理整套前端样式体系。整个过程花了两周,其中一周在拆自己以前写的代码。

如果你也遇到过「改一个按钮的颜色,结果列表页的边框变了」这种事,这篇大概对你有用。

样式为什么会先崩

样式问题从来不是突然出现的,它有三个非常典型的前兆。

症状一:同一个颜色有四种写法

打开样式表,会看到 #7C3AED、#7c3aed、rgb(124 58 237) 和 hsl(262 83% 58%) 同时存在,都表示同一个紫。它们最初都来自同一个设计稿,只是每个人复制的时候顺手改了写法。

症状二:间距靠感觉

margin: 14px、padding: 13px、gap: 1.1rem 散落在各个组件里。数值本身不算错,但它们之间没有关系,所以页面上到处是「差两像素」的错位。

症状三:改一处影响一片

这是最危险的:某个类名被多个页面复用,但复用的方式各不相同。改动它就像拆炸弹——你永远不知道谁会跟着炸。

Note

前两个症状只是难看,第三个症状才是必须重构的信号。当一个改动的影响范围无法预测时,样式体系就已经失效了。

第一步:把设计变量收进一层

重构的第一步不是删代码,而是把所有裸值收进一层变量。原则很简单:组件里只允许出现变量,不允许出现具体的颜色值和尺寸值。

色板:一层色相加明度阶梯

与其定义二十个命名颜色,不如定义「一个色相 + 一套明度阶梯」。这样换主题时只改一个色相数值,整站配色会一起跟着走:

:root {
/* 主色:一个色相,若干明度 */
--primary-hue: 255;
--primary: hsl(var(--primary-hue) 70% 55%);
--primary-hover: hsl(var(--primary-hue) 70% 48%);
--primary-light: hsl(var(--primary-hue) 70% 92%);
/* 中性色:同一色相、低饱和度,比纯灰更耐看 */
--text-90: hsl(var(--primary-hue) 12% 12%);
--text-75: hsl(var(--primary-hue) 10% 30%);
--text-50: hsl(var(--primary-hue) 8% 50%);
--line-divider: hsl(var(--primary-hue) 12% 88%);
}

关键在于中性色也带上色相:纯灰(hsl(0 0% 50%))放在有色调的界面上会显得脏,而带一点点主色相的中性色会自然很多。

间距与圆角:给一套有限的台阶

间距不要自由取值,给一套 6–8 个台阶就够,所有间距从台阶里挑:

变量数值典型用途
--space-14px图标与文字的间隙
--space-28px标签内边距
--space-312px卡片内元素间距
--space-416px卡片内边距
--space-624px区块之间的间距
--space-832px页面大区块间距
--space-1248px首屏与页脚

圆角同理:--radius-sm(6px)、--radius-md(10px)、--radius-lg(14px)、--radius-full。台阶之外不允许出现新数值,这条纪律比变量本身更重要。

在 Tailwind 里落地

用 Tailwind 的话,不必放弃实用类,只要把变量接到主题配置里:

// tailwind.config 里的关键部分
export default {
theme: {
extend: {
colors: {
primary: "var(--primary)",
"primary-hover": "var(--primary-hover)",
},
spacing: {
// 把台阶映射成 Tailwind 的间距刻度
13: "var(--space-13, 3.25rem)",
},
borderRadius: {
lg: "var(--radius-lg)",
},
},
},
};

这样写有两个好处:组件里仍然用 bg-primary p-4 rounded-lg 这样直观的类名;颜色的真实来源只有一个变量,换主题时不必全站替换。

第二步:断点与栅格

断点不是越多越好

见过最夸张的项目定义了九个断点,结果每个断点都要单独调一次布局,维护成本直接翻倍。实际上大部分内容型站点只需要三到四个:

断点宽度覆盖的设备
默认0 起手机竖屏
sm640px手机横屏、小平板
md768px平板竖屏
lg1024px桌面

再往上通常不需要新的断点,只需要限制内容的最大宽度——让正文宽度保持在 65–75 个字符之间,比给他加断点更有效。

内容优先的调整顺序

调整布局时,按这个顺序改,返工最少:

  1. 先让内容能读:正文行宽、行高、段落间距;
  2. 再让结构能站:栅格换列、侧栏折叠;
  3. 最后调装饰:圆角、阴影、动效。

反过来先调阴影和圆角,内容一改就又得重来。

Tip

测试时不要只拖浏览器宽度,一定要在真机上看一遍。手机上最常见的两个问题是:横向滚动条(多半是某个固定宽度元素溢出)和被虚拟键盘顶起来的输入框。

第三步:组件样式的三条纪律

纪律一:组件内不写魔法数字

/* 不好:14px 是从哪来的?没人知道 */
.card__title {
margin-bottom: 14px;
}
/* 好:来自间距台阶,改台阶就能全站同步 */
.card__title {
margin-bottom: var(--space-3);
}

纪律二:状态成组出现

一个交互元素至少有四种状态:默认、悬停、激活、禁用;如果它是链接,还有访问过;如果支持键盘,还要有焦点态。写样式时把这几种状态一次写完,不要等出问题再补:

.button {
background: var(--primary);
color: white;
transition: background-color 150ms ease;
}
.button:hover { background: var(--primary-hover); }
.button:active { transform: translateY(1px); }
.button:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }
.button:disabled { opacity: .5; cursor: not-allowed; }

纪律三:样式随组件走

样式写在组件文件里(scoped style),而不是在全局样式表里按类名遥控组件的内部结构。全局样式表只应该做三件事:定义变量、重置默认样式、处理跨组件的排版规则(例如 Markdown 正文)。

第四步:暗色模式怎么收尾

暗色模式做起来快,做对慢。核心思路是用变量推导,而不是逐个元素覆盖。

只在变量层切换

:root {
--card-bg: hsl(var(--primary-hue) 30% 99%);
--text-90: hsl(var(--primary-hue) 12% 12%);
}
.dark {
--card-bg: hsl(var(--primary-hue) 18% 10%);
--text-90: hsl(var(--primary-hue) 10% 92%);
}

这样一来,组件层完全不需要知道当前是哪种模式。

两件最容易漏的事

  1. 对比度:暗色模式下把深色文字换成浅色文字时,很容易忽略「次级文字」——它在浅色模式下是 60% 灰,直接搬到暗色背景上会低到看不清。次级文字的对比度建议不低于 4.5<1>。
  2. 媒体素材:纯白背景的图片、透明度为 1 的壁纸、亮色主题的代码高亮,都会在暗色模式下显得刺眼。处理方式是给图片容器加一层极淡的遮罩、给壁纸加上可配置的不透明度,代码高亮则直接切换主题。
Caution

不要用 CSS 滤镜去「反色」处理图片——肤色、品牌色和截图里的 UI 都会变得诡异。宁可给图片加遮罩,也不要整体反色。

维护:让体系活下来

重构完成只是开始,体系能不能撑住要看后面几个月的维护。

每周一次的样式巡检

花十分钟做三件事,收益极高:

  • 搜一遍裸色值(# 开头的十六进制)与裸像素值,看有没有新出现的漏网之鱼;
  • 检查是否有新的类名重复定义(同一组件两处样式互相覆盖);
  • 在暗色模式下把主要页面点一遍,重点是表格、代码块和表单。

什么时候该重构

出现下面任意一条,就该安排重构了:

  • 改一个样式需要同时动三处以上;
  • 无法回答「这个颜色变量到底影响哪些页面」;
  • 新页面的样式要靠复制旧页面再删改来产出;
  • 每次改完都要在四个断点、两种主题下手工检查一遍。

重构不等于重写。这次我做的是「先建立变量层、再逐页替换」,全程没有推翻任何页面结构,也就没有出现长期无法发布的分支。能小步走的重构,不要用大爆炸的方式做。

回头看,样式问题本质上是约定问题:变量只有一个来源、间距只有一套台阶、状态一次写完、样式跟着组件走。这四条守住之后,代码量其实差不多,但改动的可预测性完全不同——你终于能回答「改这里会影响什么」这个问题了。

从一个静态博客的样式混乱,聊到一套撑得住的前端样式体系
/posts/long-article/
作者
游音
发布于
2026-08-20
许可协议
CC BY-NC-SA 4.0

部分信息可能已经过时