Primitive Tokens 深度解析:设计系统的原始地基
【免费下载链接】ui-ux-pro-max-skillAn AI skill that provides design intelligence for building professional UI/UX across multiple platforms.项目地址: https://gitcode.com/gh_mirrors/ui/ui-ux-pro-max-skill
Primitive Tokens(原始设计令牌)是任何设计系统最底层的"地基"——它们定义了一组不带语义含义的原始设计值(颜色、间距、字号、圆角、阴影、动效时长、层级等),上层所有语义令牌(Semantic Tokens)和组件令牌(Component Tokens)都通过var()引用它们。本文基于 ui-ux-pro-max-skill 仓库中 primitive-tokens.md 的完整定义,逐一拆解每个原始令牌族的作用、命名约定、使用场景与底层实现支撑。读完本文,你将掌握:如何搭建一套可扩展的 primitive token 体系、如何理解它与语义层/组件层的关系、如何用仓库配套脚本生成与校验令牌,以及如何避免最常见的"硬编码污染"陷阱。
1. 什么是 Primitive Tokens
在 token-architecture.md 定义的三层令牌体系中,Primitive Tokens 位于最底层:
┌─────────────────────────────────────────┐ │ Component Tokens │ Per-component overrides │ --button-bg, --card-padding │ ├─────────────────────────────────────────┤ │ Semantic Tokens │ Purpose-based aliases │ --color-primary, --spacing-section │ ├─────────────────────────────────────────┤ │ Primitive Tokens │ Raw design values │ --color-blue-600, --space-4 │ └─────────────────────────────────────────┘核心特征:Primitive Tokens 是"不带语义的原始值"。--color-blue-600: #2563EB只表达"这是蓝色系第 600 号色值",而不表达"这个颜色应该用在按钮上"。语义层(--color-primary: var(--color-blue-600))和组件层(--button-bg: var(--color-primary))才负责"含义赋值"。
| 层级 | 用途 | 何时变更 |
|---|---|---|
| Primitive | 基础值(颜色、尺寸) | 极少变更——它们是地基 |
| Semantic | 语义赋值 | 主题切换(亮/暗) |
| Component | 组件定制 | 按组件需求定制 |
这带来的核心收益是可替换性:当品牌从蓝色换为绿色时,只需改动 primitive 层的--color-blue-*系列值,语义层与组件层无需任何修改;同理,暗色主题只需在.dark作用域重定义语义令牌,primitive 层原封不动。
2. 颜色令牌(Color Scales)
2.1 灰色阶(Gray Scale)
灰色阶是全套令牌中使用频率最高的色系,承担背景、边框、次级文字等大量中性角色。primitive 文档给出从 50 到 950 的完整梯度:
:root { --color-gray-50: #F9FAFB; --color-gray-100: #F3F4F6; --color-gray-200: #E5E7EB; --color-gray-300: #D1D5DB; --color-gray-400: #9CA3AF; --color-gray-500: #6B7280; --color-gray-600: #4B5563; --color-gray-700: #374151; --color-gray-800: #1F2937; --color-gray-900: #111827; --color-gray-950: #030712; }数字越小越浅、越大越深。在语义层中,这组值被大量引用:--color-background: var(--color-gray-50)(亮色背景)、--color-foreground: var(--color-gray-900)(正文前景)、--color-border: var(--color-gray-200)(边框)、--color-muted-foreground: var(--color-gray-500)(弱化文字)等,详见 semantic-tokens.md。
提示:primitive 层的灰阶在亮/暗双主题中扮演"镜像"角色——暗色模式下
--color-background变为--color-gray-950、--color-foreground变为--color-gray-50,而 primitive 值本身不变,这正是"原始值不动、语义值变换"的典型体现。
2.2 主色系(Primary Colors - Blue)
主色选用蓝色系,提供 50–900 九个色阶:
:root { --color-blue-50: #EFF6FF; --color-blue-100: #DBEAFE; --color-blue-200: #BFDBFE; --color-blue-300: #93C5FD; --color-blue-400: #60A5FA; --color-blue-500: #3B82F6; --color-blue-600: #2563EB; --color-blue-700: #1D4ED8; --color-blue-800: #1E40AF; --color-blue-900: #1E3A8A; }其中 500/600/700/800 在交互状态中承担关键角色:
--color-primary: var(--color-blue-600)—— 主要操作色--color-primary-hover: var(--color-blue-700)—— hover 加深--color-primary-active: var(--color-blue-800)—— 按下再加深--color-ring: var(--color-blue-500)—— 焦点环
为什么每个色系要保持完整梯度?因为语义层与组件层需要"同色系不同明度"来表达交互状态。若只用单一色值,hover/active/focus 状态将无差异化,组件将失去可感知的反馈层次。
2.3 状态色(Status Colors)
状态色直接映射到产品级反馈场景,primitive 层只定义 500/600 两档,语义层再补充 foreground:
:root { /* Success - Green */ --color-green-500: #22C55E; --color-green-600: #16A34A; /* Warning - Yellow */ --color-yellow-500: #EAB308; --color-yellow-600: #CA8A04; /* Error - Red */ --color-red-500: #EF4444; --color-red-600: #DC2626; /* Info - Blue */ --color-info: var(--color-blue-500); }注意--color-info这里直接引用了主色系的 blue-500——这说明 primitive 层允许跨色系引用,它只是"原始值仓库",不承担语义命名职责。语义层在此基础上完成最终映射:
--color-success: var(--color-green-600); --color-warning: var(--color-yellow-500); --color-error: var(--color-red-600); --color-destructive: var(--color-red-600);3. 间距令牌(Spacing Scale)
3.1 4px 基准单位系统
primitive 文档明确说明间距采用4px 基准单位系统,所有间距值都是 4px 的整数倍(含 0.5 倍档位):
:root { --space-0: 0; --space-px: 1px; --space-0-5: 0.125rem; /* 2px */ --space-1: 0.25rem; /* 4px */ --space-1-5: 0.375rem; /* 6px */ --space-2: 0.5rem; /* 8px */ --space-2-5: 0.625rem; /* 10px */ --space-3: 0.75rem; /* 12px */ --space-3-5: 0.875rem; /* 14px */ --space-4: 1rem; /* 16px */ --space-5: 1.25rem; /* 20px */ --space-6: 1.5rem; /* 24px */ --space-7: 1.75rem; /* 28px */ --space-8: 2rem; /* 32px */ --space-9: 2.25rem; /* 36px */ --space-10: 2.5rem; /* 40px */ --space-12: 3rem; /* 48px */ --space-14: 3.5rem; /* 56px */ --space-16: 4rem; /* 64px */ --space-20: 5rem; /* 80px */ --space-24: 6rem; /* 96px */ }为什么用 rem 而非 px?rem 以根元素字号为基准,用户调整浏览器默认字号(无障碍场景常见)时,间距、字号会等比缩放,保证整体比例不变。注释中的 px 值仅供设计还原参考。
使用规律:4px 整数倍使不同组件、不同页面之间的留白保持视觉节奏统一。语义层在此基础上定义
--spacing-component(组件内距)、--spacing-section(区块间距)、--spacing-page-x/y(页面边距)等更高层别名。真实工程中,用--space-*替换散落的margin: 12px、padding: 24px是消除"硬编码污染"的第一步。
4. 字体令牌(Typography Scale)
4.1 字号(Font Sizes)
:root { --font-size-xs: 0.75rem; /* 12px */ --font-size-sm: 0.875rem; /* 14px */ --font-size-base: 1rem; /* 16px */ --font-size-lg: 1.125rem; /* 18px */ --font-size-xl: 1.25rem; /* 20px */ --font-size-2xl: 1.5rem; /* 24px */ --font-size-3xl: 1.875rem; /* 30px */ --font-size-4xl: 2.25rem; /* 36px */ --font-size-5xl: 3rem; /* 48px */ }该梯度与 Tailwind 默认字号刻度完全一致,便于后续映射到 Tailwind 主题(见 tailwind-integration.md)。语义层基于它构建标题/正文/标签体系:
--font-heading: var(--font-size-2xl); --font-heading-lg: var(--font-size-3xl); --font-body: var(--font-size-base); --font-caption: var(--font-size-xs);4.2 行高(Line Heights)
:root { --leading-none: 1; --leading-tight: 1.25; --leading-snug: 1.375; --leading-normal: 1.5; --leading-relaxed: 1.625; --leading-loose: 2; }行高使用无单位的倍数而非固定像素,这是最佳实践:行高与字号成比例,字号变化时行距自动适配。标题通常用 tight/snug(紧凑),正文用 normal/relaxed(舒展),引用块或说明文字可用 loose。
4.3 字重(Font Weights)
:root { --font-weight-normal: 400; --font-weight-medium: 500; --font-weight-semibold: 600; --font-weight-bold: 700; }字重同样以数值令牌抽象,避免在组件中直接写font-weight: 600。组件层典型用法是--button-font-weight: var(--font-weight-medium)。
4.4 字间距(Letter Spacing)
:root { --tracking-tighter: -0.05em; --tracking-tight: -0.025em; --tracking-normal: 0; --tracking-wide: 0.025em; --tracking-wider: 0.05em; }字间距使用em单位(相对字号),常用场景:大标题压缩字距(tight/tighter)提升紧凑感,小号标签或全大写按钮放宽字距(wide/wider)提升可读性。
5. 圆角令牌(Border Radius)
:root { --radius-none: 0; --radius-sm: 0.125rem; /* 2px */ --radius-default: 0.25rem; /* 4px */ --radius-md: 0.375rem; /* 6px */ --radius-lg: 0.5rem; /* 8px */ --radius-xl: 0.75rem; /* 12px */ --radius-2xl: 1rem; /* 16px */ --radius-3xl: 1.5rem; /* 24px */ --radius-full: 9999px; }圆角刻度从 0 到胶囊形(9999px)。组件层映射示例:按钮--button-radius: var(--radius-md)、卡片--card-radius: var(--radius-lg)、徽章--badge-radius: var(--radius-full)。--radius-full用于胶囊按钮、Tag、头像等完全圆角元素。
注意:primitive 文档中
--radius-default: 0.25rem(4px)与 token-architecture 中示例的 0.5rem 略有差异,应以 primitive-tokens.md 为准——它是本仓库该体系的最终定义来源。
6. 阴影令牌(Shadows)
:root { --shadow-none: none; --shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05); --shadow-default: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1); --shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1); --shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1); --shadow-xl: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1); --shadow-2xl: 0 25px 50px -12px rgb(0 0 0 / 0.25); --shadow-inner: inset 0 2px 4px 0 rgb(0 0 0 / 0.05); }阴影采用 Tailwind 同款的两层阴影组合(主阴影 + 偏移方向阴影),透明度使用rgb(0 0 0 / 0.05)现代空格语法。层级用途:sm用于按钮等小元素轻微浮起,default用于卡片默认态,md用于卡片 hover 提升,lg/xl用于弹窗(如组件层--dialog-shadow: var(--shadow-lg)),2xl用于强调浮层。
7. 动效令牌(Motion / Duration)
:root { --duration-75: 75ms; --duration-100: 100ms; --duration-150: 150ms; --duration-200: 200ms; --duration-300: 300ms; --duration-500: 500ms; --duration-700: 700ms; --duration-1000: 1000ms; /* Semantic durations */ --duration-fast: var(--duration-150); --duration-normal: var(--duration-200); --duration-slow: var(--duration-300); }这是 primitive 文档中唯一的"半语义"定义:底层提供从 75ms 到 1000ms 的完整时间梯度,同时预置三个语义别名(fast/normal/slow)。在组件中应优先使用语义别名,例如按钮过渡transition: background var(--duration-fast)。较长的 500/700/1000ms 用于页面级动画或进度类动效。
8. 层级令牌(Z-Index Scale)
:root { --z-auto: auto; --z-0: 0; --z-10: 10; --z-20: 20; --z-30: 30; --z-40: 40; --z-50: 50; --z-dropdown: 1000; --z-sticky: 1100; --z-modal: 1200; --z-popover: 1300; --z-tooltip: 1400; }z-index 是最容易被滥用为魔数的令牌族。primitive 层同时提供数值刻度(0–50,步进 10)与语义层级的"安全区"(1000 起每层 +100)。这样设计的原因:数值刻度用于页面内相对层级,而 1000+ 语义档位用于浮层体系,保证 dropdown/sticky/modal/popover/tooltip 之间有稳定的层级差,避免出现z-index: 999999之类的魔数。
9. 命名约定与完整令牌清单
primitive 令牌统一遵循--{category}-{item}-{variant}结构(详见 token-architecture.md 的命名约定章节):
| 类别 | 命名模式 | 示例 |
|---|---|---|
| color | --color-{色系}-{梯度} | --color-gray-100、--color-blue-600 |
| space | --space-{数值} | --space-4、--space-2-5 |
| font-size | --font-size-{档位} | --font-size-sm、--font-size-2xl |
| leading | --leading-{风格} | --leading-tight、--leading-normal |
| font-weight | --font-weight-{档位} | --font-weight-semibold |
| tracking | --tracking-{风格} | --tracking-wide |
| radius | --radius-{档位} | --radius-lg、--radius-full |
| shadow | --shadow-{档位} | --shadow-md |
| duration | --duration-{毫秒} | --duration-200 |
| z | --z-{数值/语义} | --z-40、--z-modal |
10. 在工程中落地:从 JSON 到 CSS 再到校验
10.1 用模板 JSON 定义原始令牌
primitive 文档给出的是最终 CSS 形态,而仓库提供的 design-tokens-starter.json 是其上游 JSON 数据源,采用 W3C Design Tokens Community Group 的$value/$type格式。primitive 层在 JSON 中位于顶层primitive键下:
{ "primitive": { "color": { "gray": { "50": { "$value": "#F9FAFB", "$type": "color" } }, "blue": { "600": { "$value": "#2563EB", "$type": "color" } } }, "spacing": { "4": { "$value": "1rem", "$type": "dimension" } }, "fontSize": { "sm": { "$value": "0.875rem", "$type": "dimension" } } } }语义层通过{primitive.color.blue.600}这样的引用语法指向原始值,组件层再引用语义层。
10.2 生成 CSS
仓库配套脚本 generate-tokens.cjs 将上述 JSON 一键生成 CSS 变量文件:
node scripts/generate-tokens.cjs --config tokens.json -o tokens.css脚本工作流程(源码可见):
parseArgs()解析-c/--config、-o/--output、-f/--format(css | tailwind)参数;resolveReference()递归解析{primitive.color.blue.600}形式的引用链;flattenTokens()将嵌套 JSON 扁平化为--category-item-variant形式的 CSS 变量;generateCSS()按PRIMITIVES → SEMANTIC → COMPONENTS → DARK MODE四段输出;generateTailwind()将语义层--color-*变量转换为 Tailwindtheme.extend.colors片段。
--format tailwind可进一步生成 Tailwind 颜色配置:
node scripts/generate-tokens.cjs --config tokens.json --format tailwind10.3 校验:杜绝硬编码
validate-tokens.cjs 用于扫描代码库,找出本应使用令牌却写成魔数的地方:
node scripts/validate-tokens.cjs --dir src/ node scripts/validate-tokens.cjs --dir src/ --fix # 仅提示建议,不自动改写它检测四类违规:硬编码 hex 颜色(#RGB/#RRGGBB)、硬编码 RGB 颜色、两位及以上像素值、CSS 中的硬编码 rem 值,并跳过tailwind.config、globals.css、tokens.css/json等令牌定义文件。仓库测试 test_validate_tokens.py 验证了关键回归场景:即使一行中同时存在var(--...)引用和硬编码值,硬编码值仍会被正确标记为违规,而纯令牌行不会产生误报。
11. 常见误区与最佳实践
综合 primitive 文档、语义层文档与校验脚本的规则,落地时需注意:
- 组件中绝不直接使用 primitive 值。
.card { background: var(--color-gray-50) }是错误用法,应写为var(--color-card)(语义层)。primitive 只应被语义层/组件层引用,组件消费语义层,这是 semantic-tokens.md 明确强调的规则。 - 颜色禁用裸 hex。SKILL.md 的最佳实践明确指出"Never use raw hex in components - always reference tokens"。
- 间距/字号一律走 token,从校验脚本的
pixelValue/remValue规则可见,除 0 与 1px 外的散落像素值和 rem 值都会被标记。 - 语义别名优先:动效使用
--duration-fast/normal/slow而非直接写150ms;层级使用--z-modal而非1200。 - primitive 是"极少变更"层:品牌换色只改 primitive 色值,主题切换只改 semantic 覆盖(
.dark { --color-background: var(--color-gray-950); ... }),组件层保持零改动。
12. 相关阅读
- primitive-tokens.md:本文主题,全部原始令牌定义
- token-architecture.md:三层令牌架构与命名约定
- semantic-tokens.md:语义层如何引用 primitive 并支撑暗色主题
- component-tokens.md:组件层如何消费语义层
- tailwind-integration.md:将令牌映射到 Tailwind 配置
- design-tokens-starter.json:三层令牌 JSON 模板
- generate-tokens.cjs 与 validate-tokens.cjs:生成与校验脚本
- SKILL.md:设计系统技能总览与最佳实践
【免费下载链接】ui-ux-pro-max-skillAn AI skill that provides design intelligence for building professional UI/UX across multiple platforms.项目地址: https://gitcode.com/gh_mirrors/ui/ui-ux-pro-max-skill
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考