Skip to content

样式方案

样式的历史是一部"作用域与复用"的斗争史:全局选择器 → BEM → CSS Modules → 原子化 → 设计令牌。2026 年的合理姿势是四者并存:原生现代 CSS 打底、CSS Modules/组件样式局部化、原子化工具提速、令牌系统统一品牌变量。本文逐一展开,并在最后给出一套可以落地的组合方案。

一、现代 CSS 特性(2026 年的原生能力)

大部分过去需要预处理器/JS 才能做的事,原生 CSS 已经能做:

特性解决的问题可用性
原生嵌套 &取代预处理器嵌套2023 起主流浏览器支持
@layer层叠管理(框架/组件/自定义样式优先级)全绿
@container + container-type容器查询(按组件宽度而非视口响应)全绿
:has()父级选择器("包含某子元素的父元素")全绿
color-mix() / oklch()颜色计算与感知均匀的颜色空间全绿
:where() / :is()零权重/方便的选择器分组全绿
light-dark()一套值同时适配明暗(配合 color-scheme2024 起逐步普及
text-wrap: balance/pretty标题断行均衡、正文断行更优全绿
subgrid子网格对齐2023 起可用
view-transition API跨页面/状态过渡全绿(渐进增强)
css
/* 原生嵌套:必须有 & 才表示"自身",否则是后代选择器 */
.card {
  color: oklch(0.3 0.1 260);

  & h2 { font-weight: 600; }         /* .card h2 */
  &:hover { border-color: var(--accent); }
  & > .meta { font-size: 0.85em; }
}

/* :has —— 父级反向选择:含大图的卡片更宽 */
.card:has(> img.hero) { grid-column: span 2; }

/* 容器查询:以 .widget 宽度为断点,而非视口 */
.widget { container-type: inline-size; }
@container (min-width: 480px) { .widget { display: grid; grid-template-columns: 1fr auto; } }

坑 1:原生嵌套写 &:hover 容易忘 & 前缀的语义差异(不带 & 是后代选择器);嵌套过深会显著增加选择器复杂度——保持 1~2 层。 坑 2color-mix()oklch() 与现代特性配合渐进增强:旧浏览器给兜底值,新浏览器用 @supports 升级,而不是反向。

二、CSS Modules 与框架局部样式

把样式作用域收到组件,避免全局污染与命名苦役:

css
/* Button.module.css */
.root { display: inline-flex; gap: .5rem; }
.primary { background: var(--brand); }
tsx
// React(Vite 开箱支持 *.module.css)
import styles from './Button.module.css'
export function Button({ variant = 'primary' }) {
  return <button className={`${styles.root} ${styles[variant]}`}>确定</button>
}
方案机制适用
CSS Modules编译期为类名加哈希React/Vue SFC 均可,配合组件化
Vue <style scoped>属性选择器(data-v-xxx)限域Vue SFC 内建
CSS-in-JS(vanilla-extract)构建期把 TS 编译成静态 CSS想用变量/主题推导又不想运行时成本
css
/* Vue scoped 编译示意:.title[data-v-abc123] { ... } —— 依赖子元素能命中属性 */
:deep(.child) { color: red; }   /* 穿透 scoped:样式作用到子组件内部根元素之外时用 */

坑 3:Vue scoped 对子组件内部默认不透出——需要 :deep();React CSS Modules 对第三方库内部样式无能为力,要覆盖库样式得靠全局层 + @layer 管理优先级(见 §5)。

三、原子化(Utility-First):Tailwind CSS 与 UnoCSS

3.1 原子化为什么流行

把"一条样式规则"抽象成单一用途的 class:flexp-4text-sm。收益:不再起类名、天然避免死 CSS、改动只动模板;代价是学习一套速记体系,并接受"样式出现在模板里"。

3.2 Tailwind CSS v4(CSS-first 配置)

v4 起配置从 tailwind.config.js 迁到 CSS 内,内置 Rust 引擎,用 @import "tailwindcss" 单行启用:

css
/* app.css —— Tailwind v4 全配置就是 CSS */
@import "tailwindcss";

@theme {
  --color-brand: #4f46e5;          /* 定义设计令牌 → 生成 bg-brand / text-brand 等 */
  --font-sans: "Inter", ui-sans-serif, system-ui;
}

@custom-variant dark (&:where(.dark, .dark *));  /* 自定义暗色变体(默认媒体查询或 .dark) */
html
<button class="rounded-lg bg-brand px-4 py-2 text-white hover:bg-brand/90">
  提交
</button>
  • 变体:hover:dark:md:group-hover:has-[:checked]: 等;
  • 任意值:grid-cols-[200px_1fr]max-w-[theme(--breakpoint-lg)]
  • bg-brand/90 透明度语法直接消费 --color-* 令牌。

3.3 UnoCSS:按需生成的原子化引擎

UnoCSS 不是"又一个 Tailwind",而是可编程的原子化生成器:只按使用到的 class 生成 CSS(体积最小化),规则可用 preset 或自定义:

ts
// uno.config.ts
import { defineConfig, presetUno, presetAttributify } from 'unocss'
export default defineConfig({
  presets: [presetUno()],                       // Tailwind/Wind 兼容子集
  rules: [
    ['bg-brand', { background: 'var(--color-brand)' }],
    [/^m-(\d+)$/, ([, n]) => ({ margin: `${Number(n) * 4}px` })],  // 自定义动态规则
  ],
  shortcuts: { 'card-shell': 'rounded-lg border border-gray-200 shadow-sm p-4' },
})
html
<div card-shell flex="~ items-center justify-between">
  <span text-xl font-medium>标题</span>
</div>

Tailwind vs UnoCSS 一句话:工程基建偏重/想 Vite 插件即插即用 → UnoCSS 更轻;要生态与文档成熟度、团队标准统一 → Tailwind 更稳。两者都会做"扫描 + 按需生成",不要再手动维护一份 utility 长 CSS

四、设计令牌与主题系统

4.1 三层令牌模型

例子用途
基础令牌(primitive)--gray-100: #f4f4f5品牌原始色板、间距、圆角尺寸
语义令牌(alias)--color-surface: var(--gray-100)--color-text-emphasis组件只消费语义名,换肤只改映射
组件令牌--btn-bg: var(--color-primary)组件内引用,允许组件级覆盖
css
/* tokens.css */
:root {
  --color-primary: #4f46e5;
  --color-bg: #fff;
  --color-text: #18181b;
  color-scheme: light;
}
@media (prefers-color-scheme: dark) {
  :root { --color-bg: #09090b; --color-text: #fafafa; color-scheme: dark; }
}
/* light-dark() 让组件不用写两遍(需要 color-scheme 生效) */
.card { background: light-dark(#fff, #18181b); color: light-dark(#18181b, #fafafa); }

坑 4:token 别直连品牌数值写进组件——组件只认语义 token,否则"换肤/暗色/多品牌"时要全局改组件。这是设计系统里最高频返工点。 坑 5:暗色用媒体查询切换后,页面根上 background 要同步(否则原生控件、color-scheme 与滚动条仍是浅色)。

4.2 框架落地

  • Tailwind v4@theme 即令牌源,暗色变体默认跟随系统/自定义 .dark
  • UnoCSStheme 预设对象 + 变体插件;
  • 组件库联动:把语义 CSS 变量挂到 :root,框架层(MUI/AntD/Element Plus 的主题算法)也映射同一批变量,避免两套色板漂移。

五、可落地的组合方案

  1. 基线@layer 把优先级显式化——reset < tokens < components < utilities,Tailwind v4 的 @import "tailwindcss" 自带分层;
  2. 组件样式:CSS Modules(React)/ <style scoped>(Vue),复杂组件内部用现代特性写原生 CSS;
  3. 快速布局与一次性样式:原子化(工程选 Tailwind 或 UnoCSS);
  4. 品牌/主题:语义 CSS 变量 + 明暗 color-scheme;需要 JS 动态换肤时提供 data-theme 属性切换变量集;
  5. 收尾检查:构建产物里不应出现"没用到的 CSS"(原子化引擎保证),也不应出现两条规则抢同一个优先级而靠 !important 压(用 @layer 排序代替)。

六、常见坑速查

  1. 原生嵌套忘 &(§1 坑 1);2. 无兜底地使用新特性(渐进增强方向反了);
  2. scoped/Modules 管不到子组件与第三方库内部(用 :deep/@layer 全局层);
  3. token 与色板两套体系漂移(组件直写品牌数值);
  4. 暗色只改文字不改 color-scheme,原生控件刺眼;
  5. 原子化与组件样式互相覆盖:把"不可覆盖"(品牌规则)放后加载的 layer,方向反了必打架;
  6. 过度抽象 utility 化业务:把 card-hover-shadow-2 之类一次性语义硬塞进 shortcuts,得不偿失;
  7. !important 满地走:优先级战争是设计问题,用 @layer 顺序解决。

状态与参考

  • 状态:已收录(2026-09-02,由前端领域规划清单「样式方案|Tailwind / UnoCSS、CSS Modules、设计令牌」转正式)。
  • 版本:现代 CSS 特性均为 2026 年主流稳定能力(嵌套/@layer/容器查询/:has()/color-mix() 等);Tailwind v4 为 CSS-first 配置范式;UnoCSS 示例为当前推荐 API。示例为标准用法,复制到 Vite 工程(+ @tailwindcss/vite 或 unocss/vite)即可运行;版本细节以官方文档为准。
  • 参考Tailwind CSS 官方文档(v4 安装与 @theme)、UnoCSSMDN CSS 参考(嵌套/容器查询/color-mix)、Style Dictionary(令牌)
  • 阅读联动:与构建/体积优化的关系见构建工具性能优化(CLS/字体/图片);设计令牌也可对照 Vue 3 生态 中组件库章节。

下一步

  • [ ] 用一个 Vite + Vue/React 工程把 §5 组合方案完整落地并跑构建(含 @layer 优先级验证)
  • [ ] 补一篇"语义化 CSS vs 原子化 vs 混合"的团队落地决策笔记
  • [ ] 对照主题系统写暗色/多主题实现对比(CSS 变量 vs light-dark() vs 框架主题算法)

写作规范请参阅领域概览

基于 VitePress 构建 · 内容以知识共享方式沉淀