首页
/ airi 项目中的 UnoCSS Rules 完全指南:从静态工具类到符号级自定义规则

airi 项目中的 UnoCSS Rules 完全指南:从静态工具类到符号级自定义规则

2026-09-08 19:42:56作者:傅爽业Veleda

UnoCSS 是 airi 全栈仓库中几乎所有前端应用(stage-web、stage-pocket、stage-tamagotchi、stage-ui 组件库等)所依赖的即时原子化 CSS 引擎,而 Rules(规则)正是其生成一切 CSS 工具类的核心机制:它定义了 m-1bg-dotted-[neutral-200/60] 这类类名与最终 CSS 之间的映射。本文以官方技能文档 core-rules.md 为骨架,结合 airi 仓库根目录 uno.config.ts 与各应用的配置源码,完整讲解静态规则、动态规则、CSS 回退值、特殊符号、多选择器规则、完全控制规则、规则排序与合并等全部主题。读完后,你将掌握如何阅读、扩展 airi 中现有的 UnoCSS 规则体系,并能独立为项目编写符合最佳实践的自定义规则。

Rules 在 UnoCSS 中的位置:一切工具类的来源

UnoCSS 的设计哲学是「核心无观点(un-opinionated)」,它本身不内置任何工具类,所有 CSS 工具类都由 presets(预设) 提供,而预设的本质正是批量注册规则。在 airi 根目录 uno.config.tssharedUnoConfig() 中可以看到这一架构的直接体现:

  • presetWind3() 注册了 Tailwind CSS v3 / Windi CSS 兼容的整套内置规则(m-*p-*bg-* 等);
  • presetAttributify()presetTypography()presetIcons()presetScrollbar()presetChromatic() 继续追加各自的规则;
  • 配置底部的 rules: [...] 数组则是为 airi 项目定制化新增的规则。

因此理解 Rules 的两种形态(静态与动态)与三条输出通路(CSS 对象、2D 数组、模板字符串),就等于掌握了 UnoCSS 可扩展性的全部底层原理。

静态规则:最简单的类名到 CSS 映射

静态规则是「类名 → CSS 声明对象」的简单映射,直接以元组形式写在 rules 数组中:

rules: [
  ['m-1', { margin: '0.25rem' }],
  ['font-bold', { 'font-weight': 700 }],
]

在模板中使用 <div class="m-1"> 时,UnoCSS 会生成:

.m-1 { margin: 0.25rem; }

airi 仓库中就有这样真实的静态规则案例。在 apps/stage-web/uno.config.ts 中,stage-web 应用定义了一个用于关闭过渡动画的静态规则:

rules: [
  ['transition-colors-none', {
    'transition-property': 'color, background-color, border-color, text-color',
    'transition-duration': '0s',
  }],
],

关键注意事项(原文档重点强调):CSS 属性名必须使用连字符语法(font-weight 而非 fontWeight),且含连字符的属性名在对象字面量中必须加引号,否则会被 JavaScript 解析为减法表达式。这一约定在 airi 的配置中也被严格遵守,例如根配置中的 '-webkit-mask-image' 同样带引号。

动态规则:用正则表达式批量生成工具类

静态规则无法覆盖无限变化的取值(如 m-1m-100)。动态规则用 RegExp 匹配器 + 函数体 解决这个问题:

rules: [
  // 匹配 m-1、m-2、m-100 等
  [/^m-(\d+)$/, ([, d]) => ({ margin: `${d / 4}rem` })],
  // 可访问 theme 与上下文
  [/^p-(\d+)$/, (match, ctx) => ({ padding: `${match[1] / 4}rem` })],
]

函数接收两个参数:

  1. RegExp 匹配结果(通过解构 [, d] 快速取出捕获组);
  2. 上下文对象(ctx),包含 themesymbols 等属性,用于读取设计令牌或访问符号。

airi 中的真实动态规则:mask、bg-dotted 与 drag-region

airi 根目录 uno.config.ts 中注册了三个极具代表性的动态规则,是理解动态规则的最佳实战素材:

rules: [
  // 1. 从带下划线的参数生成 mask-image,如 mask-[linear-gradient(...)]
  [/^mask-\[(.*)\]$/, ([, suffix]) => ({ '-webkit-mask-image': suffix.replace(/_/g, ' ') })],

  // 2. 生成点阵背景,依赖 theme 与颜色解析工具
  [/^bg-dotted-\[(.*)\]$/, ([, color], { theme }) => {
    const parsedColor = parseColor(color, theme)
    return {
      'background-image': `radial-gradient(circle at 1px 1px, ${colorToString(parsedColor?.cssColor ?? parsedColor?.color ?? color, 'var(--un-background-opacity)')} 1px, transparent 0)`,
      '--un-background-opacity': parsedColor?.cssColor?.alpha ?? parsedColor?.alpha ?? 1,
    }
  }],

  // 3. Electron 窗口拖拽区域
  [/drag-region/, () => ({ 'app-region': 'drag' })],
],

这三个规则分别演示了动态规则的不同能力维度:

  • mask-[...] 展示了 参数捕获与字符串变换:将类名中的 _ 还原为空格,用于构造 mask-image;
  • bg-dotted-[...] 展示了 与 theme 的深度联动:通过 parseColor(color, theme) 解析形如 neutral-200/60 的颜色参数(含透明度),再配合 colorToString 生成 radial-gradient 点阵背景,并把解析出的透明度写入 CSS 自定义属性 --un-background-opacity。这正是动态规则可以「读取设计系统、输出复杂 CSS」的最佳例证;
  • drag-region 是一个无捕获组的正则规则,无论类名是 drag-region 还是 drag-region-anything 都会命中,为 Electron 应用输出 app-region: drag,让该元素可作为窗口拖拽手柄。

动态规则在组件中的实际使用

这些自定义规则并非摆设,它们在 stage-ui 组件库中被真实消费。例如 packages/stage-ui/src/components/menu/icon-item.vue 中通过 --at-apply 指令使用了点阵背景规则:

--at-apply: 'bg-dotted-[neutral-200/60] hover:bg-dotted-[primary-300/50] dark:bg-dotted-[neutral-700/25] dark:hover:bg-dotted-[primary-200/20]';

可以看到 bg-dotted-[...] 不仅能与 hover:dark: 变体自由组合,还能接受 neutral-200/60 这类「带透明度的语义色」参数——动态规则与 变体(Variants) 的协同能力在这里体现得淋漓尽致。

CSS 回退值:用 2D 数组保证浏览器兼容

当同一属性需要输出多个值做浏览器回退时,规则函数可以返回 2D 数组

rules: [
  [/^h-(\d+)dvh$/, ([_, d]) => [
    ['height', `${d}vh`],
    ['height', `${d}dvh`],
  ]],
]

生成的 CSS 会按数组顺序依次输出,让不支持 dvh 的旧浏览器回退到 vh

.h-100dvh { height: 100vh; height: 100dvh; }

这种模式适用于所有「新单位/新特性 + 旧回退」的场景,例如 svh/lvhcolor-mix() 等渐进增强型 CSS 特性。

特殊符号(symbols):从 @unocss/core 精细控制 CSS 输出

当规则需要突破「对象键值」的局限时,可以引入 @unocss/coresymbols 常量。最常见的用途是把 @supports 之类的包裹器挂到属性上:

import { symbols } from '@unocss/core'

rules: [
  ['grid', {
    [symbols.parent]: '@supports (display: grid)',
    display: 'grid',
  }],
]

可用符号一览(完整表格)

Symbol 作用说明
symbols.parent 父级包裹器(如 @supports@media
symbols.selector 修改选择器的函数
symbols.layer 设置该规则输出的 UnoCSS 层
symbols.variants 变体处理器数组
symbols.shortcutsNoMerge 在 shortcuts 中禁用合并
symbols.noMerge 禁用规则合并
symbols.sort 覆盖排序顺序
symbols.body 完全掌控 CSS body 输出

这些符号与 层(Layers) 概念紧密结合:UnoCSS 默认有 shortcutsutilities 等层,symbols.layer 可以像 rules 元组第三项 { layer: 'utilities' } 一样把规则定向到指定层,从而精确控制输出顺序。

多选择器规则:用生成器函数产出多条 CSS

当一条工具类需要生成多条(通常是不同选择器下的)CSS 规则时,把规则函数写成 generator(生成器函数),用 yield 逐条产出:

rules: [
  [/^button-(.*)$/, function* ([, color], { symbols }) {
    yield { background: color }
    yield {
      [symbols.selector]: selector => `${selector}:hover`,
      background: `color-mix(in srgb, ${color} 90%, black)`
    }
  }],
]

使用 button-red 会同时生成两条规则:

.button-red { background: red; }
.button-red:hover { background: color-mix(in srgb, red 90%, black); }

这一写法非常适用于「一个类名 + 一组配套伪类/后代选择器」的场景,例如按钮、卡片等需要常态与悬停态同时定义的组件类。注意这里用到了 color-mix() 现代 CSS 特性,与上文的回退值技巧配合可以获得更稳的兼容性。

完全控制规则:直接返回 CSS 字符串

当对象表达力不足(例如需要输出 ::after 伪元素、@media 嵌套),可以返回原始 CSS 字符串,获得对输出的完全控制:

import { defineConfig, toEscapedSelector as e } from 'unocss'

rules: [
  [/^custom-(.+)$/, ([, name], { rawSelector, theme }) => {
    const selector = e(rawSelector)
    return `
${selector} { font-size: ${theme.fontSize.sm}; }
${selector}::after { content: 'after'; }
@media (min-width: ${theme.breakpoints.sm}) {
  ${selector} { font-size: ${theme.fontSize.lg}; }
}
`
  }],
]

这里有两个关键细节:

  • 必须用 toEscapedSelector(即 e)处理 rawSelector,因为工具类名中可能含有 :/[ 等需要 CSS 转义的字符(如 custom-hover: 会生成 hover\: 选择器);
  • 可以自由读取 theme 中的设计令牌(theme.fontSize.smtheme.breakpoints.sm),实现「响应式 + 语义化」的输出。

⚠️ 原文档明确警告:完全控制规则不兼容变体(如 hover:custom-red 不会生效),因为变体需要修改选择器,而返回字符串时选择器已被写死。

symbols.body:在保留变体支持的同时输出自定义 CSS

若既要自定义 CSS 结构、又希望变体仍能工作,应改用 symbols.body 而非字符串返回。body 中可以用 & 引用当前选择器:

rules: [
  ['custom-red', {
    [symbols.body]: `
      font-size: 1rem;
      &::after { content: 'after'; }
      & > .bar { color: red; }
    `,
    [symbols.selector]: selector => `:is(${selector})`,
  }]
]

symbols.selector 负责把变体产生的选择器包装进 :is()symbols.body 则用 & 作为当前选择器的占位符。这样 hover:custom-red 这类变体组合依旧可用——这是「完全控制规则」与「变体支持」二者不可兼得时推荐的替代方案。

规则排序与优先级

  • 后声明的规则优先级更高:UnoCSS 按 rules 数组顺序处理,后面的规则会覆盖前面规则的同名属性输出;
  • 动态规则在组内按字母序输出:同一组动态规则匹配出的多个类,最终 CSS 会按工具类名字母序排列。

结合 core-layers.md 可知,跨层级的顺序由 layers 配置决定(如 components: -1default: 1utilities: 2),而层内排序遵循上述规则。airi 根配置将自定义规则放在 presets 之后声明,正是为了确保 mask-[...]bg-dotted-[...]drag-region 这些定制类拥有覆盖预设规则同属性输出的更高优先级。

规则合并:相同 CSS body 的去重与 noMerge

UnoCSS 会自动合并拥有相同 CSS body 的规则。以下标记:

<div class="m-2 hover:m2">

会合并生成一条选择器列表:

.hover\:m2:hover, .m-2 { margin: 0.5rem; }

这一机制显著减小了最终 CSS 体积。若某些规则不希望被合并(例如依赖声明顺序才能生效的规则),用 symbols.noMerge 显式关闭合并。与之相关的还有 shortcuts 中的 symbols.shortcutsNoMerge——短横线(Shortcuts) 展开的多个工具类默认参与合并,可用该符号在特定 shortcut 上禁用。需要提醒的是,所有规则的合并/去重都发生在构建时,而非运行时,因此不会产生任何额外的浏览器开销。

延伸:与 Shortcuts、Variants 的协同

Rules 是 UnoCSS 的最小单元,但它通常与仓库技能文档中的另外两个核心概念搭配使用:

  • Shortcuts:把多条规则组合成单个语义化类名(如 btn: 'py-2 px-4 font-semibold rounded-lg shadow-md'),并支持正则动态 shortcut([/^btn-(.*)$/, ([, c]) => \bg-${c}-400 ...`])。Shortcuts 在构建时展开为普通规则,因此天然兼容全部变体(hover:btndark:btn`);
  • Variants:变体是规则的「前置处理器」,把 hover:m-2 逐步拆解后交给规则匹配,再把变体转换应用到最终选择器上。上文 bg-dotted-[...]hover:dark: 的自由组合正是这一协同机制的实战证明。

三者组合起来,可以在 airi 的每个应用(stage-web、stage-pocket、stage-tamagotchi)中构建出「语义化类名 + 无限取值 + 状态响应」的完整工具类体系。

小结:在 airi 中实践规则开发

回顾 airi 仓库中的真实证据,可以总结出在项目中编写规则的实用清单:

  1. 静态规则适用于固定取值,写法最简,见 apps/stage-web/uno.config.tstransition-colors-none
  2. 动态规则适用于无限取值与 theme 联动,见 uno.config.tsmask-[...]bg-dotted-[...]drag-region
  3. 需要 @media/@supports 包裹时用 symbols.parent;需要多条选择器时用生成器函数 + symbols.selector
  4. 需要完全自定义 CSS 时返回字符串,但注意其与变体不兼容;需要变体兼容则改用 symbols.body
  5. 始终遵守「连字符属性名 + 加引号」的书写约定,动态规则中处理颜色等主题令牌时,可参考 parseColor + colorToString(来自 unocss/preset-mini)的标准解析链路;
  6. 依赖 CSS 属性书写顺序生效的规则,记得用 symbols.noMerge 关闭合并。

更多周边主题可继续阅读同目录下的 core-config.md(配置总览)、core-theme.md(设计令牌)、core-safelist.md(强制包含类)与 preset-mini.md(最小预设内置规则),形成对 UnoCSS 规则体系的完整认知。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
docsdocs
暂无描述
Markdown
899
5.83 K
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.14 K
2.76 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
860
1.35 K
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
925
1.85 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.84 K
1.02 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
533
601
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.37 K
1.46 K
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
548
395
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.04 K
525