CSS 架构与命名规范

中等 🟡CSS 布局
8 个标签
预计阅读时间:72 分钟
CSSBEMOOCSSSMACSSITCSS架构TailwindCSS-in-JS

CSS 架构与命名规范

良好的 CSS 架构和命名规范,对于大型项目的可维护性至关重要。它能避免样式冲突、提高复用性,让团队协作时"改一处不炸全局"。

为什么 CSS 需要"架构"

CSS 有两个先天特性,让它在大项目中极易失控:

1.全局作用域:任何一条选择器都作用于全站,一个 `.title` 可能被十个页面共享,改动牵一发而动全身。
2.层叠与特异性:谁生效取决于选择器权重和书写顺序,堆到后期常出现"不得不加 !important"的窘境。

一个没有架构的项目,CSS 会随时间演变成"只增不删"的巨石——没人敢删任何一行,因为不知道会影响谁。据统计,大型遗留项目中平均有 30%~60% 的 CSS 代码是永远不会命中的"死样式"。架构方法论正是为对抗这种熵增而生。

主流方法论一览

| 方法论 | 核心思想 | 解决的问题 | 适用场景 |

|---|---|---|---|

| BEM | 用命名表达结构关系 | 命名冲突、可读性 | 组件化项目 |

| OOCSS | 结构与外观分离 | 复用性 | 需要高复用的 UI |

| SMACSS | 按角色分类样式 | 组织混乱 | 中大型传统项目 |

| ITCSS | 按特异性分层 | 特异性失控 | 大型可扩展项目 |

| Utility-First | 原子类组合 | 冗余、命名负担 | 快速迭代产品 |

| CSS-in-JS | 样式绑定组件 | 作用域隔离 | React/Vue 组件应用 |

这些方法论并非互斥,实际项目常组合使用,例如 ITCSS 的分层里用 BEM 命名组件、用 Utility 类做微调。

BEM 命名规范

概念(Block-Element-Modifier):

Block(块):独立、可复用的组件,如 `.button`、`.card`
Element(元素):块的组成部分,不能脱离块存在,用 `__` 连接
Modifier(修饰符):块或元素的外观/状态变体,用 `--` 连接

命名规则:

Block:`.block`
Element:`.block__element`
Modifier:`.block--modifier` 或 `.block__element--modifier`

示例:

`.button`(块)
`.button__icon`(元素)
`.button--primary`(修饰符)
`.button__icon--large`(元素修饰符)

为什么用双下划线和双连字符? 为了让"连字符分词"(如 `.main-menu`)与"BEM 分隔符"不混淆——单个 `-` 用于单词内分隔,`__` 和 `--` 才是结构分隔符。

优点: 结构关系清晰、天然避免命名冲突、选择器扁平(特异性低且稳定)、新人看类名即懂 DOM 结构。

💻 代码示例:BEM 命名实践

cssCode
/* 块 */
.card {
  background-color: white;
  border-radius: 8px;
  box-shadow: 0 2px 4px rgba(0,0,0,0.1);
  padding: 20px;
}

/* 元素 */
.card__header {
  margin-bottom: 15px;
  padding-bottom: 15px;
  border-bottom: 1px solid #eee;
}
.card__title {
  margin: 0;
  font-size: 1.25rem;
  font-weight: 600;
}
.card__body { margin-bottom: 15px; }
.card__text {
  margin: 0 0 10px 0;
  line-height: 1.6;
  color: #666;
}
.card__footer {
  display: flex;
  justify-content: flex-end;
  gap: 10px;
}

/* 修饰符 */
.card--featured { border: 2px solid #007bff; }
.card--compact  { padding: 10px; }
.card__title--large { font-size: 1.5rem; }
htmlCode
<!-- HTML 使用:类名即文档结构 -->
<div class="card card--featured">
  <div class="card__header">
    <h2 class="card__title card__title--large">标题</h2>
  </div>
  <div class="card__body">
    <p class="card__text">内容文本</p>
  </div>
  <div class="card__footer">
    <button class="card__button">按钮</button>
  </div>
</div>

BEM 反模式提醒: 不要写出 `.card__header__title` 这样的多层元素嵌套。BEM 的 element 只表示"属于某个 block",不表示 DOM 层级,正确写法是 `.card__title`,哪怕它在 header 里面。

OOCSS(面向对象的 CSS)

两大原则:

分离结构与外观:布局骨架(尺寸、定位)与视觉皮肤(颜色、边框)拆成不同类,自由组合。
分离容器与内容:组件样式不依赖它所处的位置,`.button` 无论放在头部还是侧栏都长一样。

💻 代码示例:OOCSS

cssCode
/* 结构类:只管布局骨架 */
.media {
  display: flex;
  align-items: flex-start;
}
.media__object { margin-right: 1rem; }
.media__body {
  flex: 1;
  overflow: hidden;
}

/* 外观类(皮肤):只管视觉,可任意叠加 */
.media--bordered {
  border: 1px solid #ddd;
  padding: 1rem;
  border-radius: 4px;
}
.media--spaced { margin-bottom: 1rem; }
htmlCode
<!-- 组合使用:结构 + 多个外观皮肤 -->
<div class="media media--bordered media--spaced">
  <img class="media__object" src="avatar.jpg" alt="头像">
  <div class="media__body">
    <h3>标题</h3>
    <p>内容文本</p>
  </div>
</div>

SMACSS(可扩展的模块化 CSS 架构)

SMACSS 把所有样式按"角色"归为五类,用命名前缀区分职责:

Base:基础样式(重置、元素默认样式)
Layout:布局样式(网格、容器),前缀 `l-` 或 `layout-`
Module:可复用组件,占样式的绝大部分
State:状态样式(激活、禁用、隐藏),前缀 `is-` 或 `has-`
Theme:主题样式(颜色、字体皮肤),前缀 `theme-`

💻 代码示例:SMACSS 分层

cssCode
/* Base - 基础样式 */
* { box-sizing: border-box; }
body {
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
  line-height: 1.6;
  color: #333;
}

/* Layout - 布局样式(l- 前缀) */
.l-container {
  max-width: 1200px;
  margin: 0 auto;
  padding: 0 20px;
}
.l-grid { display: grid; gap: 20px; }

/* Module - 模块样式 */
.card {
  background-color: white;
  border-radius: 8px;
  box-shadow: 0 2px 4px rgba(0,0,0,0.1);
  padding: 20px;
}
.button {
  padding: 10px 20px;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  transition: all 0.3s ease;
}

/* State - 状态样式(is- 前缀) */
.is-active   { background-color: #007bff; color: white; }
.is-hidden   { display: none; }
.is-disabled { opacity: 0.5; cursor: not-allowed; }

/* Theme - 主题样式(theme- 前缀) */
.theme-dark  { background-color: #1a1a1a; color: #fff; }
.theme-light { background-color: #fff; color: #333; }

ITCSS(倒三角 CSS)

ITCSS 的精髓是把样式表按特异性从低到高、作用域从广到窄分层组织,像一个倒三角形:

1.Settings:变量和配置(CSS 变量、SCSS 变量)
2.Tools:混合(mixin)和函数,不产生实际输出
3.Generic:重置和标准化(normalize、reset)
4.Elements:裸 HTML 元素样式(h1、a、p)
5.Objects:无外观的布局结构(如 .o-container)
6.Components:具体 UI 组件(.c-button)
7.Utilities:辅助原子类,特异性最高,可加 !important

为什么这个顺序重要? CSS 后写的覆盖先写的。按此顺序组织,越具体、越需要"赢"的样式越靠后,天然形成合理的覆盖关系,从根本上避免了特异性大战。

💻 代码示例:ITCSS 层次 + 命名前缀

cssCode
/* 1. Settings:设计令牌 */
:root {
  --color-primary: #007bff;
  --space-md: 16px;
  --radius: 8px;
}

/* 4. Elements:裸元素 */
a { color: var(--color-primary); text-decoration: none; }

/* 5. Objects:o- 前缀,只做布局 */
.o-container {
  max-width: 1200px;
  margin-inline: auto;
  padding-inline: var(--space-md);
}

/* 6. Components:c- 前缀,具体组件 */
.c-button {
  padding: 10px 20px;
  border-radius: var(--radius);
  background: var(--color-primary);
  color: #fff;
}

/* 7. Utilities:u- 前缀,最高优先级 */
.u-hidden { display: none !important; }
.u-text-center { text-align: center !important; }

实用工具类(Utility-First)

概念: 用大量小而单一职责的原子类,在 HTML 中组合出复杂 UI,代表框架是 Tailwind CSS。不再为每个组件起名、写单独 CSS,而是直接堆类名。

示例:

`.p-4 { padding: 1rem; }`
`.text-center { text-align: center; }`
`.flex { display: flex; }`

优点: 开发速度快、无需为命名纠结、样式高度一致(受设计令牌约束)、配合 PurgeCSS 后产物体积极小。

代价: HTML 类名冗长、初期学习曲线陡、复杂交互仍需抽象成组件。

💻 代码示例:工具类

cssCode
/* 间距工具类 */
.p-1 { padding: 0.25rem; }
.p-2 { padding: 0.5rem; }
.p-3 { padding: 1rem; }
.p-4 { padding: 1.5rem; }
.m-1 { margin: 0.25rem; }
.m-2 { margin: 0.5rem; }
.m-3 { margin: 1rem; }
.m-4 { margin: 1.5rem; }

/* 文本工具类 */
.text-center { text-align: center; }
.text-left   { text-align: left; }
.text-right  { text-align: right; }
.text-sm   { font-size: 0.875rem; }
.text-base { font-size: 1rem; }
.text-lg   { font-size: 1.125rem; }
.text-xl   { font-size: 1.25rem; }

/* 颜色工具类 */
.text-primary   { color: #007bff; }
.text-secondary { color: #6c757d; }
.text-success   { color: #28a745; }
.text-danger    { color: #dc3545; }
.bg-primary { background-color: #007bff; }
.bg-danger  { background-color: #dc3545; }

/* 布局工具类 */
.flex { display: flex; }
.flex-col { flex-direction: column; }
.items-center   { align-items: center; }
.justify-center { justify-content: center; }
.justify-between{ justify-content: space-between; }
htmlCode
<!-- 组合使用:无需为这个卡片写任何专属 CSS -->
<div class="p-4 bg-white rounded shadow">
  <h2 class="text-xl text-primary mb-2">标题</h2>
  <p class="text-base text-secondary mb-4">内容文本</p>
  <div class="flex justify-between">
    <button class="px-4 py-2 bg-primary text-white rounded">确定</button>
    <button class="px-4 py-2 bg-secondary text-white rounded">取消</button>
  </div>
</div>

CSS-in-JS 与 CSS Modules

概念: 在 JavaScript/组件中书写样式,实现真正的作用域隔离与动态样式。

主流方案对比:

| 方案 | 隔离方式 | 运行时开销 | 动态样式 | 代表 |

|---|---|---|---|---|

| CSS Modules | 编译时生成唯一类名 | 无 | 弱 | `*.module.css` |

| Styled-components | 运行时注入 | 有 | 强 | React |

| Emotion | 运行时/编译时 | 较小 | 强 | React |

| 零运行时方案 | 编译时提取 | 无 | 中 | Vanilla Extract、Linaria |

💻 代码示例:CSS Modules 与 Styled-components

jsxCode
/* CSS Modules:类名自动哈希,绝不冲突 */
/* Button.module.css -> .primary { background: #007bff; } */
import styles from './Button.module.css';

function Button() {
  return <button className={styles.primary}>确定</button>;
}
jsxCode
/* Styled-components:样式即组件,支持 props 动态化 */
import styled from 'styled-components';

const Button = styled.button`
  padding: 10px 20px;
  border-radius: 4px;
  background: ${(props) => (props.primary ? '#007bff' : '#6c757d')};
  color: #fff;
`;

// <Button primary>确定</Button>

真实案例:从 !important 泥潭到 ITCSS 重构

场景: 某电商中台项目历时三年,CSS 文件累积到 480KB,全局 `!important` 出现 700 多次,新人改个按钮颜色要试五六个选择器才生效。

根因: 无分层、无命名规范,选择器互相打架,只能靠 `!important` 硬压,进一步加剧混乱。

重构方案: 引入 ITCSS 分层 + BEM 命名 + 设计令牌(CSS 变量),配合 PurgeCSS 清除死样式。

成果数据:

| 指标 | 重构前 | 重构后 | 改善 |

|---|---|---|---|

| CSS 体积(gzip 后) | 480KB → 96KB | 130KB → 22KB | 减少 77% |

| !important 数量 | 700+ | 12 | 减少 98% |

| 平均选择器层级 | 4.2 层 | 1.3 层 | 更扁平 |

| 新样式上线返工率 | 约 35% | 约 8% | 大幅下降 |

关键不在于用了哪个"银弹",而在于建立了可预测的覆盖顺序统一的命名约定

文件组织策略

按功能组织: `components/`、`layouts/`、`pages/`、`utilities/`

按类型组织: `base/`、`components/`、`layouts/`、`themes/`

ITCSS 组织: `settings/`、`tools/`、`generic/`、`elements/`、`objects/`、`components/`、`utilities/`

深入:特异性(Specificity)与层叠

所有架构方法论对抗的核心敌人,都是"特异性失控"。理解特异性的计算规则,才能明白为什么方法论要那样设计。

特异性用一个四元组 (a, b, c, d) 表示,从左到右比较大小:

| 权重位 | 对应选择器 | 示例 |

|---|---|---|

| a(内联) | style 属性 | `style="..."` |

| b(ID) | #id | `#header` |

| c(类/属性/伪类) | .class、[attr]、:hover | `.btn`、`[disabled]` |

| d(元素/伪元素) | div、::before | `a`、`::after` |

cssCode
/* 特异性对比:谁的权重高谁生效,与书写顺序无关 */
#nav .list li a       { color: red; }   /* (0,1,1,2) 权重高 */
.nav .list .item .link{ color: blue; }  /* (0,0,4,0) 权重反而低! */
/* 结果:即使蓝色写在后面,红色因含 ID 仍胜出 */

/* 架构的目标:让选择器尽量保持在 (0,0,1,0) 这一层 */
.link { color: blue; } /* 扁平单类,特异性稳定、易覆盖 */

这正是 BEM 用扁平单类、ITCSS 按特异性升序分层的根本原因——保持特异性低且可预测,覆盖时才不需要靠 !important 硬刚。

现代方案:@layer 级联层

CSS 原生的 `@layer` 特性把"覆盖顺序"从"选择器权重比拼"升级为"显式层级声明",堪称 ITCSS 思想的语言级实现。层的声明顺序决定优先级,后声明的层整体覆盖先声明的层,与选择器特异性无关

cssCode
/* 一次性声明层的优先级顺序(越靠后优先级越高) */
@layer reset, base, components, utilities;

@layer components {
  /* 即便这里用了高特异性选择器 */
  .card .title#main { color: black; }
}

@layer utilities {
  /* 这个低特异性的类依然能覆盖上面的 —— 因为 utilities 层更靠后 */
  .text-red { color: red; }
}

有了 @layer,第三方库样式、业务组件、工具类之间的覆盖关系可以被明确编排,从此告别"为了盖过 UI 框架不得不写 !important"的窘境。

CUBE CSS:新一代混合方法论

CUBE CSS(Composition Utility Block Exception)是近年兴起的务实方法论,主张"拥抱层叠而非对抗它":

Composition(组合):负责宏观布局骨架(如 flow、grid 布局原语)
Utility(工具):单一职责的原子类做微调
Block(块):处理无法用前两者表达的组件特有样式
Exception(例外):用 data 属性表达状态变体,如 `[data-state="active"]`
cssCode
/* Composition:通用间隔布局原语 */
.flow > * + * { margin-block-start: var(--flow-space, 1rem); }

/* Utility:原子类 */
.text-center { text-align: center; }

/* Block:组件特有样式 */
.card { border-radius: 8px; padding: var(--space-md); }

/* Exception:用 data 属性表达状态 */
.card[data-state="featured"] { border: 2px solid var(--color-primary); }

CSS 变量驱动的主题系统

用 CSS 自定义属性(变量)构建主题,是现代架构中最实用的一环——切换主题只需改根节点上的一组变量,全站自动响应,无需重复定义样式。

cssCode
/* 定义语义化令牌,组件只消费语义变量,不碰具体色值 */
:root {
  --color-bg: #ffffff;
  --color-text: #1a1a1a;
  --color-surface: #f5f5f5;
  --color-primary: #007bff;
}

/* 暗色主题:只需覆盖同名变量 */
[data-theme="dark"] {
  --color-bg: #1a1a1a;
  --color-text: #f5f5f5;
  --color-surface: #2a2a2a;
  --color-primary: #4d9fff;
}

/* 组件消费语义变量,天然支持任意主题 */
.panel {
  background: var(--color-surface);
  color: var(--color-text);
}

/* 跟随系统偏好自动切换 */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --color-bg: #1a1a1a;
    --color-text: #f5f5f5;
  }
}
javascriptCode
// 配套的主题切换逻辑:改一个属性,全站变色
function toggleTheme() {
  const root = document.documentElement;
  const next = root.dataset.theme === 'dark' ? 'light' : 'dark';
  root.dataset.theme = next;
  localStorage.setItem('theme', next); // 记住用户选择
}

用 Stylelint 强制约束规范

架构规范若不能自动执行,就会随时间腐化。Stylelint 能把命名规范、特异性上限、属性顺序等约束固化到 CI 里。

jsCode
// .stylelintrc.js
module.exports = {
  extends: ['stylelint-config-standard'],
  rules: {
    // 限制选择器最大特异性,逼团队写扁平选择器
    'selector-max-specificity': '0,3,0',
    // 禁止 ID 选择器,避免特异性飙升
    'selector-max-id': 0,
    // 强制 BEM 命名格式
    'selector-class-pattern': '^[a-z]([a-z0-9-]+)?(__([a-z0-9-]+))?(--([a-z0-9-]+))?$',
    // 禁止 !important(架构失败信号)
    'declaration-no-important': true,
  }
};

设计令牌流水线(Design Tokens)

大型设计系统会用 Style Dictionary 等工具,把一份 JSON 令牌源,一次性编译成 CSS 变量、SCSS 变量、iOS/Android 常量,实现"一处定义、多端消费"。

jsonCode
{
  "color": {
    "primary": { "value": "#007bff" },
    "danger":  { "value": "#dc3545" }
  },
  "space": {
    "md": { "value": "16px" }
  }
}
cssCode
/* 编译产出的 CSS(自动生成,勿手改) */
:root {
  --color-primary: #007bff;
  --color-danger: #dc3545;
  --space-md: 16px;
}

容器查询:组件级响应式

传统媒体查询基于视口宽度,但组件的理想响应式应基于它自己所处容器的宽度。容器查询(Container Queries)让同一个组件在侧栏和主区能自动呈现不同布局,是组件化架构的重要拼图。

cssCode
/* 声明容器 */
.card-container {
  container-type: inline-size;
  container-name: card;
}

/* 组件根据容器宽度而非视口宽度自适应 */
@container card (min-width: 400px) {
  .card { display: grid; grid-template-columns: 120px 1fr; }
}
@container card (max-width: 399px) {
  .card { display: block; }
}

清除死样式:覆盖率分析

CSS 只增不删是大项目的通病。用 Chrome DevTools 的 Coverage 面板可量化"未命中样式",再用 PurgeCSS 在构建期剔除。

jsCode
// postcss.config.js —— 生产构建时清除未使用的 CSS
module.exports = {
  plugins: [
    require('@fullhuman/postcss-purgecss')({
      content: ['./src/**/*.{html,jsx,tsx,vue}'],
      // 保护动态拼接的类名不被误删
      safelist: [/^is-/, /^has-/, /^theme-/],
    })
  ]
};

真实案例:多品牌主题化架构落地

场景:一家 SaaS 公司要给同一套产品做 5 个不同品牌的白标(white-label)版本,每个品牌配色、圆角、字体各异。最初做法是复制 5 份 CSS 分别改,维护噩梦——改一个通用组件要同步改 5 个地方。

方案:抽象出一层语义令牌,组件只消费语义变量;每个品牌只维护一份约 30 行的令牌覆盖文件。

cssCode
/* 组件层:永远只用语义变量,与品牌无关 */
.btn-primary {
  background: var(--brand-primary);
  border-radius: var(--brand-radius);
  font-family: var(--brand-font);
}

/* 品牌 A 令牌 */
[data-brand="a"] {
  --brand-primary: #ff6b00;
  --brand-radius: 4px;
  --brand-font: 'Roboto', sans-serif;
}
/* 品牌 B 令牌 */
[data-brand="b"] {
  --brand-primary: #0066cc;
  --brand-radius: 12px;
  --brand-font: 'Inter', sans-serif;
}

成果数据

| 指标 | 改造前 | 改造后 |

|---|---|---|

| 新增一个品牌工作量 | 约 3 人日 | 约 2 小时 |

| 通用组件改动同步点 | 5 处 | 1 处 |

| 主题相关 CSS 总量 | 5 × 全量 | 1 × 全量 + 5 × 令牌 |

| 品牌间视觉不一致缺陷 | 频发 | 趋近 0 |

Atomic Design:设计侧的分层方法论

Atomic Design(原子设计)是 Brad Frost 提出的组件分层思想,虽然源自设计领域,但对 CSS 架构的组件粒度划分极有指导意义。它把界面拆成五个层次:

| 层次 | 含义 | CSS 对应 | 示例 |

|---|---|---|---|

| Atoms(原子) | 最小不可分单元 | 单一元素样式 | 按钮、输入框、标签、图标 |

| Molecules(分子) | 若干原子组合 | 小型复合组件 | 搜索框(输入框+按钮) |

| Organisms(有机体) | 若干分子组合 | 大型区块组件 | 头部导航、商品卡片列表 |

| Templates(模板) | 页面骨架 | 布局层样式 | 页面栅格与占位 |

| Pages(页面) | 填入真实内容 | 页面级覆盖 | 具体某个落地页 |

为什么这个分层对 CSS 有用? 它给了组件一个清晰的"粒度尺度",避免团队里有人把整页写成一个巨型组件、有人把一个图标拆成十个类。粒度统一后,样式的复用边界才清晰。

cssCode
/* Atoms:原子级——只关心自身,不关心上下文 */
.atom-button {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.5rem 1rem;
  border: none;
  border-radius: var(--radius);
  font: inherit;
  cursor: pointer;
}
.atom-input {
  padding: 0.5rem 0.75rem;
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
}
.atom-icon {
  width: 1em;
  height: 1em;
  fill: currentColor;
}

/* Molecules:分子——由原子组合,负责它们之间的布局关系 */
.molecule-search {
  display: flex;
  gap: 0.5rem;
  align-items: center;
}
.molecule-search .atom-input { flex: 1; }

/* Organisms:有机体——较完整的功能区块 */
.organism-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 1rem 2rem;
  border-bottom: 1px solid var(--color-border);
}

Atomic Design 与 BEM 结合:原子/分子/有机体天然对应 BEM 的 block,元素对应 element,变体对应 modifier。粒度用 Atomic Design 界定,命名用 BEM 落地,两者互补。

CSS Modules 深入:composes 与全局逃逸

CSS Modules 不只是"自动加哈希类名",它还有一套组合与作用域控制机制,理解后能写出既隔离又复用的样式。

cssCode
/* base.module.css —— 抽出可复用的基础样式 */
.buttonBase {
  padding: 10px 20px;
  border-radius: 4px;
  cursor: pointer;
  border: none;
}
cssCode
/* Button.module.css —— 用 composes 组合基础样式 */
.primary {
  composes: buttonBase from './base.module.css';
  background: #007bff;
  color: #fff;
}
.danger {
  composes: buttonBase from './base.module.css';
  background: #dc3545;
  color: #fff;
}

/* :global 逃逸——需要对接第三方库的全局类名时 */
:global(.ant-btn) {
  border-radius: 8px;
}

/* :local 显式声明局部(默认即局部,用于覆盖 :global 上下文) */
:global .theme-dark {
  :local(.primary) { background: #4d9fff; }
}
jsxCode
// 消费端:多个 module 类名可以随意组合
import styles from './Button.module.css';
import cx from 'classnames';

function Button({ variant = 'primary', disabled }) {
  return (
    <button className={cx(styles[variant], { [styles.disabled]: disabled })}>
      提交
    </button>
  );
}

CSS Modules 的核心价值对比:

| 能力 | 普通 CSS | CSS Modules |

|---|---|---|

| 类名冲突 | 全局风险 | 编译期哈希,零冲突 |

| 死代码检测 | 难 | 未引用的类可被 tree-shake |

| 样式复用 | 靠命名约定 | composes 显式组合 |

| 运行时开销 | 无 | 无(编译期完成) |

| 动态样式 | 弱 | 弱(需配合内联或 data 属性) |

Sass/SCSS 架构:7-1 模式

在预处理器时代,7-1 模式是最广为采用的文件组织约定:把所有 Sass 文件按七个文件夹 + 一个主入口组织。即便现在原生 CSS 已很强大,7-1 的分类思想依然值得借鉴。

scssCode
// main.scss —— 唯一入口,按依赖顺序 @use
@use 'abstracts/variables';   // 1. 变量、令牌
@use 'abstracts/functions';   // 2. 函数
@use 'abstracts/mixins';      // 3. 混入
@use 'vendors/normalize';     // 4. 第三方
@use 'base/reset';            // 5. 重置
@use 'base/typography';       // 6. 排版
@use 'layout/grid';           // 7. 布局
@use 'layout/header';
@use 'components/button';     // 8. 组件
@use 'components/card';
@use 'pages/home';            // 9. 页面专属
@use 'themes/dark';           // 10. 主题
scssCode
// abstracts/_mixins.scss —— 沉淀可复用的响应式与工具混入
@mixin respond-to($breakpoint) {
  @if $breakpoint == 'tablet' {
    @media (min-width: 768px) { @content; }
  } @else if $breakpoint == 'desktop' {
    @media (min-width: 1024px) { @content; }
  }
}

@mixin truncate($lines: 1) {
  @if $lines == 1 {
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  } @else {
    display: -webkit-box;
    -webkit-line-clamp: $lines;
    -webkit-box-orient: vertical;
    overflow: hidden;
  }
}

// 使用
.card__title {
  @include truncate(2);
  @include respond-to('desktop') { font-size: 1.5rem; }
}
scssCode
// components/_button.scss —— 用 @each 批量生成变体,减少重复
$button-variants: (
  'primary': #007bff,
  'success': #28a745,
  'danger':  #dc3545,
);

.button {
  padding: 10px 20px;
  border-radius: 4px;

  @each $name, $color in $button-variants {
    &--#{$name} {
      background: $color;
      &:hover { background: darken($color, 10%); }
    }
  }
}

现代提醒:Sass 的 `@import` 已废弃,一律改用 `@use` / `@forward`,它们提供真正的模块作用域,避免变量全局污染。

PostCSS:CSS 处理流水线

PostCSS 是"用 JS 插件处理 CSS"的工具链核心。它不是预处理器,而是一个可编排的转换管线,现代 CSS 架构几乎离不开它。

jsCode
// postcss.config.js —— 典型的现代流水线配置
module.exports = {
  plugins: [
    // 1. 允许使用未来的 CSS 语法,自动降级
    require('postcss-preset-env')({
      stage: 2,
      features: {
        'nesting-rules': true,          // 原生嵌套降级
        'custom-media-queries': true,   // @custom-media
      },
    }),
    // 2. 自动补全浏览器前缀
    require('autoprefixer'),
    // 3. 生产环境压缩
    ...(process.env.NODE_ENV === 'production'
      ? [require('cssnano')({ preset: 'default' })]
      : []),
    // 4. 清除未使用的样式
    require('@fullhuman/postcss-purgecss')({
      content: ['./src/**/*.{html,jsx,tsx,vue}'],
      safelist: [/^is-/, /^has-/, /data-/],
    }),
  ],
};
cssCode
/* @custom-media:把媒体查询也令牌化,断点集中管理 */
@custom-media --tablet (min-width: 768px);
@custom-media --desktop (min-width: 1024px);

.sidebar {
  display: none;
  @media (--desktop) { display: block; }
}

PostCSS 流水线各环节收益(某中型项目实测):

| 环节 | 作用 | 收益 |

|---|---|---|

| autoprefixer | 自动前缀 | 手写前缀量降为 0 |

| postcss-preset-env | 语法降级 | 放心用新语法 |

| cssnano | 压缩 | 体积再降 15%~25% |

| purgecss | 除死代码 | 体积降 60%~80% |

关注点分离:三种耦合与解耦

CSS 架构的深层目标是降低耦合。项目里存在三种典型耦合,各有解法:

1. 结构与样式耦合(HTML 结构一变,CSS 就崩)

cssCode
/* 坏:强依赖 DOM 结构,加一层 div 就失效 */
.sidebar > div > ul > li > a { color: #333; }

/* 好:给关键节点一个语义类,结构可自由调整 */
.sidebar__link { color: #333; }

2. 内容与容器耦合(组件依赖它被放在哪)

cssCode
/* 坏:按钮在头部和侧栏必须长得不一样才对 —— 其实是耦合了位置 */
.header .button { font-size: 14px; }
.sidebar .button { font-size: 12px; }

/* 好:用 modifier 表达差异,组件自身与位置解耦 */
.button--sm { font-size: 12px; }
.button--md { font-size: 14px; }

3. 主题与组件耦合(换肤要改遍所有组件)

cssCode
/* 坏:色值散落在组件里 */
.card { border: 1px solid #ddd; background: #fff; }

/* 好:组件只消费语义变量,主题层统一供给 */
.card { border: 1px solid var(--color-border); background: var(--color-surface); }

状态管理类:is- / has- 前缀约定

组件状态(激活、加载、展开、错误)是 CSS 里最容易失控的部分。SMACSS 提倡用 `is-` / `has-` 前缀统一表达状态,让状态类一眼可辨、便于 JS 精确操作。

cssCode
/* 状态类:单一职责,只描述"处于某状态时叠加什么" */
.is-loading  { opacity: 0.6; pointer-events: none; }
.is-active   { border-color: var(--color-primary); }
.is-hidden   { display: none !important; }
.is-expanded { max-height: 1000px; }
.has-error   { border-color: var(--color-danger); }
.has-error .field__hint { color: var(--color-danger); }

/* 与组件组合:组件 + 状态,正交叠加 */
.dropdown { max-height: 0; overflow: hidden; transition: max-height 0.3s; }
.dropdown.is-expanded { max-height: 400px; }
jsCode
// JS 只负责切换状态类,不直接操作样式
const dropdown = document.querySelector('.dropdown');
button.addEventListener('click', () => {
  dropdown.classList.toggle('is-expanded');
});

现代替代方案:也可用 `data-*` 属性表达状态(CUBE CSS 的 Exception 思想),二者可并存:

cssCode
[data-state="loading"] { opacity: 0.6; }
[data-state="error"]   { border-color: var(--color-danger); }

组件框架中的样式方案

现代前端多用组件框架,各框架有自己的样式隔离机制,架构上要选定一套主方案并统一。

vueCode
<!-- Vue 单文件组件:scoped 自动加属性选择器实现隔离 -->
<template>
  <button class="btn" :class="{ 'is-loading': loading }">
    <slot />
  </button>
</template>

<style scoped>
/* 编译后变成 .btn[data-v-xxxxxx],作用域隔离 */
.btn {
  padding: 10px 20px;
  border-radius: var(--radius);
}
/* 深度选择器:需要穿透到子组件时 */
:deep(.child-class) { color: red; }
</style>
jsxCode
// React + CSS Modules:最主流的零运行时隔离方案
import styles from './Card.module.css';
export function Card({ children }) {
  return <div className={styles.card}>{children}</div>;
}
jsxCode
// React + Vanilla Extract:编译期 CSS-in-TS,类型安全且零运行时
import { style } from '@vanilla-extract/css';
export const card = style({
  padding: 20,
  borderRadius: 8,
  boxShadow: '0 2px 4px rgba(0,0,0,0.1)',
  ':hover': { transform: 'translateY(-2px)' },
});

方案选型决策表:

| 场景 | 推荐方案 | 理由 |

|---|---|---|

| 静态站点/内容站 | 原生 CSS + BEM + @layer | 无构建负担 |

| React 中后台 | CSS Modules | 零运行时、隔离好 |

| 高度动态主题 | CSS 变量 + 语义令牌 | 换肤一处改 |

| 设计系统/组件库 | Vanilla Extract / 原生 | 类型安全、可提取 |

| 快速迭代产品 | Tailwind | 开发速度快 |

关键渲染路径:Critical CSS 与代码分割

大型应用的 CSS 若一次性全量加载,会阻塞首屏渲染。架构上要做"关键 CSS 内联 + 其余异步"。

htmlCode
<!-- 首屏关键 CSS 内联进 <head>,避免额外请求阻塞渲染 -->
<head>
  <style>
    /* 仅包含首屏可见区域的样式,通常 <14KB */
    .header { /* ... */ }
    .hero { /* ... */ }
  </style>
  <!-- 非关键 CSS 异步加载,不阻塞首屏 -->
  <link rel="preload" href="/main.css" as="style"
        onload="this.rel='stylesheet'">
</head>
jsCode
// 按路由分割 CSS:只加载当前页需要的样式
// 现代打包器(Vite/webpack)配合动态 import 自动完成
const AboutPage = () => import('./pages/About.vue'); // 其 CSS 会被独立打包

Critical CSS 收益(某电商首页实测):

| 指标 | 优化前 | 优化后 |

|---|---|---|

| CSS 阻塞时间 | 约 480ms | 约 90ms |

| First Contentful Paint | 2.1s | 1.3s |

| 首屏 CSS 传输量 | 130KB | 内联 12KB + 异步 118KB |

渐进式迁移策略:老项目如何引入架构

推倒重来风险极高,成熟做法是"绞杀者模式(Strangler Pattern)"——新旧共存,逐步替换。

cssCode
/* 第一步:用 @layer 把老样式整体降到最低层,为新样式让路 */
@layer legacy, base, components, utilities;

@layer legacy {
  /* @import 老的巨石样式表进 legacy 层 */
}

/* 新样式写进更高的层,天然覆盖老样式,无需 !important */
@layer components {
  .c-button { /* 新版按钮 */ }
}

迁移路线图:

1.止血:新增功能一律用新规范,禁止再往老文件加代码。
2.建层:用 @layer 隔离新旧,新样式天然优先。
3.建令牌:抽出设计令牌为 CSS 变量,新旧组件共享。
4.按模块替换:每次迭代替换一个模块,用视觉回归保证不走样。
5.度量:跟踪老样式覆盖率下降曲线,直到可安全删除。

度量与治理:让架构可量化

架构好不好不能靠感觉,要有可跟踪的指标,纳入 CI 报表。

| 指标 | 健康值 | 工具 |

|---|---|---|

| 平均选择器特异性 | ≤ (0,1,0) | Wallace / analyze-css |

| !important 数量 | 趋近 0 | stylelint |

| 最大选择器嵌套深度 | ≤ 3 | stylelint |

| 未使用 CSS 比例 | < 10% | Coverage / PurgeCSS |

| 重复声明块数量 | 低 | csscss |

| gzip 后总体积 | 有预算约束 | bundlesize |

jsCode
// package.json 里用 bundlesize 给 CSS 体积设预算,超标 CI 报错
{
  "bundlesize": [
    { "path": "./dist/*.css", "maxSize": "30 kB" }
  ]
}
bashCode
# 用 wallace 分析一份 CSS 的健康度报告
npx wallace-cli dist/main.css
# 输出:选择器数量、特异性分布、!important 计数、重复规则等

真实案例:设计系统落地与组件库沉淀

场景:一家中型公司有 6 条产品线,每条线各自维护 UI,同一个"下拉选择器"存在 6 种实现、5 种视觉、3 种交互,用户跨产品使用时体验割裂,团队重复造轮子。

方案:成立平台团队,抽出统一设计系统与组件库,分三层落地。

cssCode
/* 第一层:令牌(Design Tokens)—— 一份 JSON 编译到多端 */
:root {
  --ds-color-primary: #2563eb;
  --ds-color-danger: #dc2626;
  --ds-space-1: 4px;
  --ds-space-2: 8px;
  --ds-space-3: 16px;
  --ds-radius-md: 8px;
  --ds-font-size-body: 14px;
}

/* 第二层:基础组件(原子/分子)—— 只消费令牌 */
.ds-select {
  height: 36px;
  padding: 0 var(--ds-space-3);
  border: 1px solid var(--ds-color-border);
  border-radius: var(--ds-radius-md);
  font-size: var(--ds-font-size-body);
}
.ds-select:focus-visible {
  outline: 2px solid var(--ds-color-primary);
  outline-offset: 1px;
}

/* 第三层:主题皮肤 —— 各产品线只覆盖令牌 */
[data-product="finance"] { --ds-color-primary: #0f766e; }
[data-product="social"]  { --ds-color-primary: #db2777; }

成果数据:

| 指标 | 落地前 | 落地后 |

|---|---|---|

| 同类组件实现份数 | 6 份 | 1 份 |

| 新产品线接入 UI 工作量 | 约 20 人日 | 约 3 人日 |

| 跨产品视觉一致性缺陷 | 每月约 40 单 | 每月 < 5 单 |

| 无障碍达标率 | 约 45% | 约 95% |

| 组件库 npm 周下载(内部) | — | 稳定复用 |

关键经验:组件库成功的前提不是代码多漂亮,而是令牌层的抽象是否稳定——令牌是设计与代码的契约,契约稳,上层才敢大规模复用。

真实案例:Tailwind 迁移的收益与代价

场景:某 SaaS 团队的传统 SCSS 工程 CSS 累积到 620KB,组件命名混乱,新人上手要背几百个自定义类名。团队决定迁移到 Tailwind。

htmlCode
<!-- 迁移前:需要在 SCSS 里定义 .promo-card 及其所有子元素 -->
<div class="promo-card">
  <h3 class="promo-card__title">标题</h3>
  <p class="promo-card__desc">描述</p>
</div>

<!-- 迁移后:原子类组合,无需写任何自定义 CSS -->
<div class="rounded-lg bg-white p-6 shadow-md">
  <h3 class="mb-2 text-xl font-semibold text-gray-900">标题</h3>
  <p class="text-sm text-gray-600">描述</p>
</div>
jsCode
// 用 @apply 抽出高频组合,兼顾原子化与复用(避免类名地狱)
// components.css
.btn-primary {
  @apply inline-flex items-center rounded-md bg-blue-600 px-4 py-2
         font-medium text-white transition hover:bg-blue-700;
}

成果与代价:

| 维度 | 结果 |

|---|---|

| 生产 CSS 体积 | 620KB → 18KB(JIT 按需生成 + Purge) |

| 新人上手命名成本 | 大幅下降(无需记业务类名) |

| 样式一致性 | 提升(受 spacing/color 令牌约束) |

| HTML 可读性 | 下降(类名冗长,需配合组件抽象) |

| 复杂动画/伪元素 | 仍需写自定义 CSS 或 plugin |

结论:Tailwind 不是银弹,它把"命名成本"换成了"HTML 冗长",适合组件化良好、能把重复类抽成组件的项目;对大量一次性页面反而可能更累。

命名规范对比实验

为了直观感受不同命名方法论的差异,用同一个"通知卡片"组件分别实现一遍。

htmlCode
<!-- 方案 A:BEM —— 语义清晰、优先级扁平 -->
<div class="notice notice--warning">
  <span class="notice__icon"></span>
  <div class="notice__content">
    <h4 class="notice__title">标题</h4>
    <p class="notice__text">内容</p>
  </div>
</div>

<!-- 方案 B:Utility —— 无需自定义 CSS,但类名冗长 -->
<div class="flex gap-3 rounded-lg border-l-4 border-yellow-400 bg-yellow-50 p-4">
  <span class="h-5 w-5 shrink-0 text-yellow-500"></span>
  <div>
    <h4 class="font-semibold text-yellow-800">标题</h4>
    <p class="text-sm text-yellow-700">内容</p>
  </div>
</div>

<!-- 方案 C:CUBE CSS —— 组合原语 + 少量组件类 + data 状态 -->
<div class="notice cluster" data-variant="warning">
  <span class="notice__icon"></span>
  <div class="stack">
    <h4>标题</h4>
    <p>内容</p>
  </div>
</div>
cssCode
/* CUBE CSS 配套:组合原语可跨组件复用 */
.cluster { display: flex; gap: var(--space-sm); }
.stack > * + * { margin-block-start: var(--space-xs); }
.notice[data-variant="warning"] {
  border-inline-start: 4px solid var(--color-warning);
  background: var(--color-warning-bg);
}

三方案量化对比:

| 维度 | BEM | Utility | CUBE CSS |

|---|---|---|---|

| 自定义 CSS 量 | 中 | 极少 | 少 |

| HTML 类名长度 | 中 | 长 | 中 |

| 复用粒度 | 组件级 | 原子级 | 混合 |

| 主题切换难度 | 中 | 低(配令牌) | 低 |

| 上手成本 | 低 | 中 | 中 |

| 适合团队规模 | 各种 | 中大型 | 中大型 |

响应式架构策略

响应式不是"到处写媒体查询",而应有整体策略。核心是"移动优先 + 断点令牌化 + 容器查询补位"。

cssCode
/* 1. 断点令牌化:断点集中定义,避免魔法数字散落 */
:root {
  --bp-sm: 640px;
  --bp-md: 768px;
  --bp-lg: 1024px;
  --bp-xl: 1280px;
}

/* 2. 移动优先:默认写窄屏样式,用 min-width 逐级增强 */
.grid {
  display: grid;
  grid-template-columns: 1fr;          /* 移动端单列 */
  gap: var(--space-md);
}
@media (min-width: 768px) {
  .grid { grid-template-columns: repeat(2, 1fr); }  /* 平板两列 */
}
@media (min-width: 1024px) {
  .grid { grid-template-columns: repeat(4, 1fr); }  /* 桌面四列 */
}

/* 3. 内在响应式:用 auto-fit + minmax 免写媒体查询 */
.auto-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: var(--space-md);
}

/* 4. 容器查询补位:组件级响应式,不依赖视口 */
.widget-area { container-type: inline-size; }
@container (min-width: 400px) {
  .widget { display: flex; }
}

响应式策略选择:

| 需求 | 推荐方案 |

|---|---|

| 页面级布局切换 | 媒体查询(移动优先) |

| 组件在不同容器自适应 | 容器查询 |

| 网格列数随宽度自动增减 | auto-fit + minmax |

| 字号/间距平滑缩放 | clamp() |

| 完全避免断点跳变 | 流式单位 + clamp |

暗黑模式的架构级实现

暗色模式若在组件里逐个写 `.dark .xxx`,会散落且难维护。正确做法是"语义令牌 + 主题层覆盖"。

cssCode
/* 1. 定义语义令牌(不是具体颜色,而是"用途") */
:root {
  color-scheme: light;
  --bg-base: #ffffff;
  --bg-elevated: #f8f9fa;
  --text-primary: #1a1a1a;
  --text-secondary: #6b7280;
  --border-subtle: #e5e7eb;
  --shadow-color: 0 0 0 / 0.1;
}

/* 2. 暗色主题:只覆盖令牌,组件代码零改动 */
[data-theme="dark"] {
  color-scheme: dark;
  --bg-base: #0f172a;
  --bg-elevated: #1e293b;
  --text-primary: #f1f5f9;
  --text-secondary: #94a3b8;
  --border-subtle: #334155;
  --shadow-color: 0 0 0 / 0.4;
}

/* 3. 跟随系统偏好(用户未手动选择时) */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    color-scheme: dark;
    --bg-base: #0f172a;
    --text-primary: #f1f5f9;
  }
}

/* 4. 组件只消费语义令牌,自动支持所有主题 */
.card {
  background: var(--bg-elevated);
  color: var(--text-primary);
  border: 1px solid var(--border-subtle);
  box-shadow: 0 4px 12px rgb(var(--shadow-color));
}
jsCode
// 主题切换:读取本地存储与系统偏好,避免刷新闪烁(FOUC)
// 这段应内联在 <head> 最前面同步执行
(function () {
  const stored = localStorage.getItem('theme');
  const prefersDark = matchMedia('(prefers-color-scheme: dark)').matches;
  const theme = stored || (prefersDark ? 'dark' : 'light');
  document.documentElement.dataset.theme = theme;
})();

为什么这样架构? 组件永远不知道"现在是什么主题",它只认令牌。新增第三套主题(如"护眼模式")时,只加一段令牌覆盖,全站组件零改动。

CSS 反模式画廊

把常见错误集中展示,对照学习最有效。

cssCode
/* 反模式 1:位置耦合——组件依赖它被放在哪 */
.homepage .sidebar .widget h3 { font-size: 18px; }   /* 坏 */
.widget__title { font-size: 18px; }                   /* 好 */

/* 反模式 2:特异性军备竞赛 */
.nav ul li a.active span { color: red !important; }   /* 坏 */
.nav__label--active { color: red; }                   /* 好 */

/* 反模式 3:魔法数字 */
.modal { top: 137px; left: 249px; }                   /* 坏 */
.modal { inset: 50%; translate: -50% -50%; }          /* 好 */

/* 反模式 4:颜色硬编码遍地开花 */
.btn { background: #3b82f6; }
.link { color: #3b82f6; }                             /* 坏:改主题要全局搜 */
.btn { background: var(--color-primary); }
.link { color: var(--color-primary); }                /* 好 */

/* 反模式 5:用 ID 写通用样式 */
#submit-button { padding: 10px; }                     /* 坏:优先级 100,难覆盖 */
.button { padding: 10px; }                            /* 好 */

/* 反模式 6:过度嵌套 */
.a .b .c .d .e { }                                    /* 坏:脆弱且慢 */
.deep-target { }                                      /* 好:扁平单类 */

CSS 快照测试与文档化

架构的持续健康离不开测试与文档。Storybook + 视觉回归 + 交互文档是标配。

jsCode
// Button.stories.js —— 用 Storybook 沉淀所有组件状态
export default { title: 'Components/Button', component: Button };

export const Primary = { args: { variant: 'primary', children: '主按钮' } };
export const Loading = { args: { loading: true, children: '加载中' } };
export const Disabled = { args: { disabled: true, children: '禁用' } };
export const AllVariants = {
  render: () => `
    <button class="button button--primary">主要</button>
    <button class="button button--secondary">次要</button>
    <button class="button button--danger">危险</button>
  `,
};
jsCode
// 配合 Chromatic 做视觉回归:每个 story 自动截图对比
// .storybook/main.js 里接入,PR 时自动跑,走样即拦截
export default {
  addons: ['@chromatic-com/storybook'],
};

文档化收益:Storybook 让样式系统"可查、可试、可回归"。新人不用翻源码,直接在 Storybook 里看到所有组件的所有状态;改动组件时视觉回归自动兜底,避免"改 A 坏 B"。

常见坑

1.BEM 元素层层嵌套:`.a__b__c` 是错的,element 不表达 DOM 层级。
2.选择器过深:`.nav ul li a span` 特异性高且脆弱,改用扁平单类。
3.滥用 !important:这是架构失败的信号,而非解决方案。
4.命名规范混用:一个项目里 BEM、驼峰、工具类混杂,团队心智负担剧增。
5.不清理死样式:不配 PurgeCSS/覆盖率分析,CSS 只增不减。
6.CSS-in-JS 忽视运行时成本:高频渲染组件用运行时方案可能拖慢首屏。

CSS 自定义属性的进阶架构模式

CSS 变量不只是"存颜色",用好它能实现许多架构级能力。

cssCode
/* 模式 1:变量兜底链——多级 fallback,令牌未定义时优雅降级 */
.card {
  padding: var(--card-padding, var(--space-md, 16px));
}

/* 模式 2:作用域覆盖——同名变量在局部重定义,实现"上下文皮肤" */
.sidebar {
  --color-primary: #7c3aed;  /* 侧栏内主色变紫,无需新类名 */
}
.sidebar .button { background: var(--color-primary); }

/* 模式 3:开关变量——用变量做 "CSS 版 if"(space toggle 技巧) */
.alert {
  --is-dismissible: ;          /* 空值 = 开 */
  --close-display: var(--is-dismissible) none;
}
.alert--permanent {
  --is-dismissible: initial;   /* initial 让下面回退 */
}

/* 模式 4:计算派生——用一个基准变量派生整套间距 */
:root { --space-unit: 8px; }
.box {
  padding: calc(var(--space-unit) * 2);   /* 16px */
  gap: calc(var(--space-unit) * 3);       /* 24px */
}

/* 模式 5:响应式变量——变量本身随媒体查询变化,组件自动跟随 */
:root { --gutter: 16px; }
@media (min-width: 1024px) { :root { --gutter: 32px; } }
.layout { padding-inline: var(--gutter); }

完整的 Stylelint 治理规则集

把架构约束固化成一份可复用的 Stylelint 配置,是团队规范落地的最后一公里。

jsCode
// .stylelintrc.js —— 生产级架构治理配置
module.exports = {
  extends: ['stylelint-config-standard', 'stylelint-config-recess-order'],
  plugins: ['stylelint-declaration-strict-value'],
  rules: {
    // 特异性与选择器约束
    'selector-max-specificity': '0,3,0',
    'selector-max-id': 0,
    'selector-max-compound-selectors': 3,     // 嵌套不超过 3 层
    'selector-no-qualifying-type': true,       // 禁止 div.foo 这种限定
    // 命名规范
    'selector-class-pattern':
      '^[a-z]([a-z0-9-]+)?(__([a-z0-9-]+))?(--([a-z0-9-]+))?$',
    // 禁止硬编码——颜色/字号必须走变量
    'scale-unlimited/declaration-strict-value': [
      ['/color/', 'font-size', 'z-index'],
      { ignoreValues: ['transparent', 'currentColor', 'inherit'] },
    ],
    // 架构失败信号
    'declaration-no-important': true,
    // 一致性
    'color-hex-length': 'short',
    'shorthand-property-no-redundant-values': true,
  },
};
jsonCode
// package.json —— 把检查挂进 lint-staged,提交即校验
{
  "lint-staged": {
    "*.{css,scss}": ["stylelint --fix", "prettier --write"]
  }
}

规则落地效果(某团队接入 3 个月):

| 指标 | 接入前 | 接入后 |

|---|---|---|

| 新增 !important | 每周约 15 处 | 0(CI 直接拦) |

| 硬编码色值 | 遍地 | 强制走令牌 |

| 选择器平均特异性 | (0,2,3) | (0,1,1) |

| 代码评审样式类意见 | 大量 | 大幅减少(机器先过一遍) |

架构选型决策流程

面对新项目,可用下面这条决策链快速定方案,避免"为了用而用":

1.项目规模多大? 单页/活动页 → 直接原生 CSS + 少量工具类即可,别上重型方法论。
2.用什么框架? React/Vue 组件应用 → 优先 CSS Modules 或框架自带 scoped,天然隔离。
3.迭代速度诉求? 极快迭代、能抽组件 → Tailwind;反之用 BEM 更易读。
4.要不要多主题/白标? 要 → 一定先建语义令牌层,再谈上层方法论。
5.团队多大? 多人协作 → 命名规范 + stylelint + @layer 分层缺一不可。
6.有历史包袱? 有 → 绞杀者模式渐进迁移,@layer 隔离新旧。

一句话:先按规模和框架选主方案,再用令牌层兜住主题,最后用工具链把规范钉死。

值得注意的是,这条决策链的每一步都不是"二选一"的排他决定,而是可叠加的。比如一个 React 中后台完全可以:主方案用 CSS Modules 做隔离,同时引入语义令牌层支持暗色模式,再用 @layer 隔离历史遗留样式,最后用 stylelint 把命名和特异性钉死。方法论是工具箱,不是宗教,按需组合才是成熟团队的做法。

最佳实践

项目启动前先定命名规范,写进团队文档并用 stylelint 强制约束。
用 CSS 变量集中管理设计令牌(颜色、间距、圆角),主题切换一处改全站变。
保持选择器扁平、低特异性,避免深层嵌套。
用 ITCSS 或类似分层保证覆盖顺序可预测。
定期用覆盖率工具和 PurgeCSS 清除死样式。
方法论可组合,因地制宜,而非教条照搬。
沉淀 Storybook 等组件文档,让样式系统"可查、可用、可维护"。
老项目用绞杀者模式渐进迁移,配 @layer 隔离新旧,切忌推倒重来。
把架构约束(特异性、!important、命名、体积预算)纳入 CI 度量,让健康度可量化。
首屏关键 CSS 内联、其余异步,按路由分割样式,别让 CSS 阻塞渲染。
暗色模式与多品牌一律走"语义令牌 + 主题层覆盖",组件零改动。
状态用 is-/has- 前缀或 data-* 属性表达,JS 只切类不直接改样式。
用 Atomic Design 统一组件粒度,用 BEM 命名落地,两者互补。
高频原子类组合用 @apply 或 composes 抽成语义类,避免类名地狱。
CSS 变量善用兜底链、作用域覆盖与派生计算,一个基准变量派生整套间距。
用 PostCSS 流水线(autoprefixer + preset-env + cssnano + purgecss)自动化处理前缀、降级、压缩与除死代码。
响应式坚持移动优先,断点令牌化,能用 auto-fit/clamp 免写媒体查询就免写。

总结

| 方法论 | 一句话记忆 | 最适场景 |

|---|---|---|

| BEM | 用类名画出组件结构,杜绝命名冲突 | 组件化项目通用默认 |

| OOCSS | 骨架与皮肤分离,最大化复用 | 高复用 UI 库 |

| SMACSS | 按角色分五类,前缀标职责 | 中大型传统项目 |

| ITCSS | 按特异性分层,让覆盖顺序可预测 | 大型可扩展项目 |

| Utility-First | 原子类组合,快到飞起 | 快速迭代产品 |

| CSS-in-JS / Modules | 样式随组件走,天然隔离 | 组件框架应用 |

| CUBE CSS | 拥抱层叠,组合+工具+块+例外 | 务实的中大型项目 |

| @layer 级联层 | 显式编排覆盖顺序 | 治理优先级、迁移老项目 |

架构治理的核心闭环可概括为四步:定令牌(设计与代码的契约)→ 分层级(用 @layer/ITCSS 让覆盖可预测)→ 控命名(BEM/前缀让特异性扁平)→ 可度量(用 stylelint/覆盖率/体积预算把规范固化进 CI)。

需要强调的是:这四步不是一次性工程,而是伴随项目全生命周期的持续治理。令牌会随设计系统演进,分层会随模块增多调整,命名规范要随新人不断宣贯,度量指标要定期回看。架构的敌人始终是"熵增"——只有把治理动作自动化、常态化,才能让 CSS 在三年、五年后依然可预测、可维护,而不是又变成那个"没人敢删一行"的巨石。

没有"最好"的架构,只有"最适合当前团队与项目规模"的架构。核心目标始终不变:让 CSS 可预测、可复用、可维护,改一处不炸全局。