本文档是 React + TypeScript + antd 项目的通用样式与设计令牌规范
本文聚焦:Design Token 三层架构、样式物理形式、CSS Module、Tailwind 边界、颜色/间距/字号/圆角映射表、主题定制、可访问性对比度。
文章末尾附md文档(可用于项目中skill)
1. 核心规则速览
核心规则速览:编写样式前必读的 12 条 checklist
- Token 化:色值/字号/间距/圆角走 Design Token 或 CSS 变量,禁止 inline 硬编码
- 三层架构:色板层 → 语义层 → 组件级,语义层才被业务消费
- 样式物理形式按特征决策:无样式单文件 / 有样式双文件 / 复杂视觉 module.less
- CSS Module 强制:类名 camelCase,禁全局选择器泄漏
- 写法优先级:CSS 变量(module.less) > useToken()(动态场景) > 硬编码(仅豁免边界)
- Tailwind 用于布局/间距/对齐工具类,不覆盖 antd 组件视觉
- 间距用 4px 基准网格,字号用固定梯度,不随意取值
- 颜色语义化:功能色(成功/警告/错误)用语义 Token,不用字面色值
- 圆角/边框/阴影统一 Token,不各组件手写
- 主题通过 ConfigProvider theme 注入 + cssVar 全局变量化
- 正文颜色对比度 ≥ 4.5:1(WCAG AA)
- 禁止 !important、禁止魔法字符串、禁止重复定义色值
2. Design Token 三层架构
Token 必须划分为 三层,业务代码只允许消费语义层,色板层和组件级由主题体系维护。
| 层级 | 职责 | 示例 | 谁消费 |
|---|---|---|---|
| 色板层 | 原始色值 + 10 级梯度(0-9),仅主题体系内部引用 | blue.palette[0..9]、gray.palette[0..9] | 主题体系 |
| 语义层 | 功能语义 Token(主题色/成功/警告/错误/文本/边框/填充) | colorPrimary、colorError、colorText | 业务代码 |
| 组件级 | 按组件维度微调(Button/Table/Tabs 等) | Table.headerBg、Button.defaultBg | antd 组件 |
核心约束:业务组件 禁止直接引用色板层字面色值,只能通过语义 Token 间接取值。这样换肤/深色模式只需调整语义层映射,无需改动业务代码。
// 色板层:原始色 + 10 级梯度(仅主题体系使用)
const blue = { primary: '#1677ff', palette: ['#e6f4ff', ..., '#003eb3'] };
const gray = { palette: ['#f5f5f5', ..., '#1f1f1f'] };
// 语义层:把色板映射到功能语义(业务消费点)
colorPrimary: blue.primary,
colorError: red.primary,
colorText: gray.palette[9],
3. 样式物理形式
按组件特征决定样式落位,避免无样式硬套双文件,也避免复杂视觉堆在单文件。
| 组件特征 | 物理形式 | 示例 |
|---|---|---|
| 无样式 / 仅布局工具类 | 单文件 .tsx + Tailwind 工具类 | 简单按钮组、对齐容器 |
| 有样式(颜色/尺寸/间距) | 文件夹 + .module.less | 卡片、状态条、标签 |
| 复杂视觉(动画/嵌套/状态修饰) | 文件夹 + .module.less(嵌套 + 状态修饰) | hover/active/disabled 态组件 |
决策原则:Tailwind 能表达的单次布局用它;涉及颜色、Token、动画、状态修饰一律进 .module.less。
4. CSS Module 规范
涉及视觉样式的组件必须使用 CSS Module(Xxx.module.less),类名通过 styles.xxx 访问,禁止全局选择器泄漏。
.header { padding: 16px; } // camelCase
.title { font-size: 14px; } // 不写 snake_case / kebab-case
.card {
padding: 16px;
&Active { border-color: var(--color-primary); } // 状态修饰
&Disabled { opacity: 0.5; cursor: not-allowed; }
}
类名规范:camelCase;状态用嵌套 &Active / &Disabled,组合用 cx(styles.card, isActive && styles.cardActive)。
全局样式边界:* {} / body {} / html {} 只允许出现在全局样式入口文件,禁止在组件 .module.less 中写全局选择器。
// 禁止:组件 module.less 中写全局选择器
body { ... } // ❌ 泄漏
* { box-sizing: ...; } // ❌ 泄漏
// 允许:全局入口文件集中 reset
/* global.less */
body { margin: 0; font-family: var(--font-family); }
5. Tailwind 使用边界
| 场景 | 选择 | 原因 |
|---|---|---|
| 单次布局(Flex/Grid 对齐) | Tailwind | 工具类即可,无需建 .less |
| 间距/对齐微调 | Tailwind | gap-3 / mt-4 简洁 |
| 简单响应式 | Tailwind | md:flex-row 一目了然 |
| 复杂视觉(动画/渐变/伪元素) | module.less | Tailwind 难以表达 |
| 状态修饰(hover/active/disabled) | module.less | 嵌套结构清晰 |
| antd 组件样式覆盖 | module.less(:global) | 精准覆盖不污染 |
antd 组件不套 Tailwind 视觉类:antd 组件视觉用 antd 自身 API(color / variant),不用 Tailwind 类覆盖颜色/边框/圆角。
// 禁止:Tailwind 类覆盖 antd 视觉
<Button className="bg-blue-500 text-white border-0">保存</Button>
// 推荐:antd 组件用 antd API
<Button color="primary" variant="solid">保存</Button>
// 允许:自定义容器用 Tailwind 布局
<div className="flex gap-3 mb-4">...</div>
入口单一:Tailwind 只在全局样式入口 @import "tailwindcss",不在组件内重复 import。
6. 颜色与 Token
6.1 写法优先级
颜色的三种写法,按可用性优先级选择:
| 优先级 | 写法 | 适用 |
|---|---|---|
| 1(默认) | CSS 变量(module.less) | var(--color-text-tertiary) |
| 2 | useToken() | 仅 less 无法表达的动态场景 |
| 3(豁免) | 固定色值硬编码 | 仅页面标题区等豁免边界 |
6.2 常见映射表
把常见硬编码映射为 Token / CSS 变量,禁止散落字面量:
| 旧硬编码 | Token | CSS 变量 |
|---|---|---|
#9ca3af | colorTextTertiary | var(--color-text-tertiary) |
#FF4D4F | colorError | var(--color-error) |
#52C41A | colorSuccess | var(--color-success) |
#FAAD14 | colorWarning | var(--color-warning) |
#1677FF | colorPrimary | var(--color-primary) |
6.3 语义色用法
.errorText { color: var(--color-error); } // 错误文案
.successBg { background: var(--color-success-bg); } // 成功底色
.warningBorder { border-color: var(--color-warning-border); } // 警告描边
禁止:业务代码出现 style={{ color: '#FF4D4F' }} 这类 inline 硬编码,除非位于豁免边界。
7. 间距、字号与圆角
7.1 间距
基于 4px 基准网格,关键档位如下:
| 场景 | 值 | Token |
|---|---|---|
| 组件内部紧凑间距 | 8px | var(--padding-sm) |
| 标准内边距 | 16px | var(--padding) |
| 卡片/弹窗外边距 | 24px | var(--padding-lg) |
| Flex gap 默认 | 12px | gap: 12px |
7.2 字号
| 场景 | 值 | Token |
|---|---|---|
| 主标题 | 20px | --font-size-heading |
| 正文/副标题 | 14px | var(--font-size) |
| 辅助文字 | 13px | var(--font-size-sm) |
| 标签/小字 | 12px | var(--font-size-xs) |
7.3 圆角与边框
.card { border-radius: var(--border-radius); } // 默认 6px
.tag { border-radius: var(--border-radius-sm); } // 小 4px
.input{ border: 1px solid var(--color-border); } // 边框用语义 Token
禁止:font-size: 15px、margin: 13px 这类不在梯度表内的随意取值。
8. 主题定制
主题通过 ConfigProvider theme 注入,并开启 cssVar 让 Token 成为全局 CSS 变量,业务样式可直接 var(--xxx) 消费。
import { ConfigProvider } from 'antd';
<ConfigProvider
theme={{
cssVar: { key: 'app' },
hashed: false,
token: {
colorPrimary: blue.primary,
colorError: red.primary,
colorText: gray.palette[9],
},
components: {
Button: { defaultBg: gray.palette[2] },
Table: { headerBg: gray.palette[0] },
},
}}
>
<App />
</ConfigProvider>
换肤能力:只要业务代码全部走语义 Token,切换主题只需替换 token 映射,组件与页面无需改动。第三方组件(非 antd)同样通过 CSS 变量消费,保证全局一致。
9. 深色模式与响应式
9.1 深色模式
深色模式通过语义 Token 翻转实现,业务代码不感知明暗,只消费语义色。
/* 亮色下 color-text = 深灰,暗色下自动翻转为浅色 */
.text { color: var(--color-text); } // 无需分支
.bg { background: var(--color-bg-container); }
约束:语义 Token 由主题体系按亮/暗分别映射;业务代码禁止写死 #fff 背景或 #000 文字(硬编码会导致深色模式失效)。
9.2 响应式
// Tailwind 响应式前缀(简单场景)
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">...</div>
复杂响应式(断点内布局重组)用 module.less 的媒体查询,断点值与 antd 栅格断点对齐。
10. 可访问性(颜色对比度)
| 场景 | 对比度 | 示例 |
|---|---|---|
| 正文文字 | ≥ 4.5:1 | 深灰 on 白底 |
| 大标题(≥18px) | ≥ 3:1 | 深色 on 浅底 |
| 交互元素边框 | ≥ 3:1 | 输入框边框 |
| 禁用状态 | 豁免 | 但仍需可辨识 |
禁止:浅灰文字 on 白底(对比度不足)、纯色背景上的同色文字。
11. 禁止项
- inline 硬编码色值(
style={{ color: '#xxx' }}),豁免边界除外 .module.less中使用全局选择器(*/body/html)- 类名 snake_case / kebab-case
!important(覆盖第三方且无其他方案时须注释说明)- 字面量色值散落在 module.less(应用 Token / CSS 变量)
- 业务代码直接消费色板层字面色值
- 同一视觉值在多处硬编码(应抽 Token)
- 组件内
import 'xxx.css'(全局样式必须集中入口) - 字号/间距取梯度表外随意值
- Tailwind 类覆盖 antd 组件视觉
12. 决策树
暂时无法在唯科之家2.0文档外展示此内容
13. 例外与豁免
豁免边界(固定色值可直接使用) :页面标题区 / 顶栏标题(如主标题 #111827、副标题 #94a3b8、提示图标 #999)。
注意:此豁免不可推广到状态卡片、列表、表单、空态等其他场景。
暂时无法在唯科之家2.0文档外展示此内容
暂时无法在唯科之家2.0文档外展示此内容