样式与设计令牌(styling.md)

2 阅读7分钟

本文档是 React + TypeScript + antd 项目的通用样式与设计令牌规范

本文聚焦:Design Token 三层架构、样式物理形式、CSS Module、Tailwind 边界、颜色/间距/字号/圆角映射表、主题定制、可访问性对比度。

文章末尾附md文档(可用于项目中skill)

1. 核心规则速览

核心规则速览:编写样式前必读的 12 条 checklist

  1. Token 化:色值/字号/间距/圆角走 Design Token 或 CSS 变量,禁止 inline 硬编码
  2. 三层架构:色板层 → 语义层 → 组件级,语义层才被业务消费
  3. 样式物理形式按特征决策:无样式单文件 / 有样式双文件 / 复杂视觉 module.less
  4. CSS Module 强制:类名 camelCase,禁全局选择器泄漏
  5. 写法优先级:CSS 变量(module.less) > useToken()(动态场景) > 硬编码(仅豁免边界)
  6. Tailwind 用于布局/间距/对齐工具类,不覆盖 antd 组件视觉
  7. 间距用 4px 基准网格,字号用固定梯度,不随意取值
  8. 颜色语义化:功能色(成功/警告/错误)用语义 Token,不用字面色值
  9. 圆角/边框/阴影统一 Token,不各组件手写
  10. 主题通过 ConfigProvider theme 注入 + cssVar 全局变量化
  11. 正文颜色对比度 ≥ 4.5:1(WCAG AA)
  12. 禁止 !important、禁止魔法字符串、禁止重复定义色值

2. Design Token 三层架构

Token 必须划分为 三层,业务代码只允许消费语义层,色板层和组件级由主题体系维护。

层级职责示例谁消费
色板层原始色值 + 10 级梯度(0-9),仅主题体系内部引用blue.palette[0..9]gray.palette[0..9]主题体系
语义层功能语义 Token(主题色/成功/警告/错误/文本/边框/填充)colorPrimarycolorErrorcolorText业务代码
组件级按组件维度微调(Button/Table/Tabs 等)Table.headerBgButton.defaultBgantd 组件

核心约束:业务组件 禁止直接引用色板层字面色值,只能通过语义 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 ModuleXxx.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
间距/对齐微调Tailwindgap-3 / mt-4 简洁
简单响应式Tailwindmd:flex-row 一目了然
复杂视觉(动画/渐变/伪元素)module.lessTailwind 难以表达
状态修饰(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)
2useToken()仅 less 无法表达的动态场景
3(豁免)固定色值硬编码仅页面标题区等豁免边界

6.2 常见映射表

把常见硬编码映射为 Token / CSS 变量,禁止散落字面量:

旧硬编码TokenCSS 变量
#9ca3afcolorTextTertiaryvar(--color-text-tertiary)
#FF4D4FcolorErrorvar(--color-error)
#52C41AcolorSuccessvar(--color-success)
#FAAD14colorWarningvar(--color-warning)
#1677FFcolorPrimaryvar(--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
组件内部紧凑间距8pxvar(--padding-sm)
标准内边距16pxvar(--padding)
卡片/弹窗外边距24pxvar(--padding-lg)
Flex gap 默认12pxgap: 12px

7.2 字号

场景Token
主标题20px--font-size-heading
正文/副标题14pxvar(--font-size)
辅助文字13pxvar(--font-size-sm)
标签/小字12pxvar(--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: 15pxmargin: 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文档外展示此内容