设计系统中的间距 Token 体系从 8px 网格到响应式间距的工程实践一、间距的随意性从像素级调整到系统化治理前端开发中间距是最容易出现不一致的视觉属性。一个页面中可能同时存在 12px、14px、16px、18px、20px 等多种间距值这些值往往由不同开发者在不同时间点凭感觉设置。当设计要求统一间距风格时需要逐个检查和调整数百个间距值。间距 Token 体系通过定义一组有限的间距变量如--spacing-xs: 4px、--spacing-sm: 8px、--spacing-md: 16px约束间距值的选择范围。但 Token 体系的设计本身需要考虑多个因素基础网格单位、缩放比例、响应式适配和组件嵌套。二、间距 Token 体系的设计原理从 8px 网格到几何级数flowchart TD A[基础网格: 4px] -- B[间距 Token 定义] B -- C[xs: 4px] B -- D[sm: 8px] B -- E[md: 16px] B -- F[lg: 24px] B -- G[xl: 32px] B -- H[2xl: 48px] B -- I[3xl: 64px] subgraph 缩放策略 J[线性缩放: 等差数列] K[几何缩放: 等比数列] L[混合缩放: 小间距线性, 大间距几何] end C -- J D -- J E -- K F -- K G -- K H -- L I -- L subgraph 响应式适配 M[移动端: 基准 × 0.75] N[平板端: 基准 × 1.0] O[桌面端: 基准 × 1.25] end B -- M B -- N B -- O8px 网格系统的核心思想所有间距值都是 4 的倍数4、8、12、16、20、24...确保视觉节奏的一致性。更精细的 4px 基准允许小间距的微调如 4px 的内边距同时保持整体网格对齐。三、生产级代码实现与最佳实践/* * 间距 Token 体系 * 基于 4px 网格混合缩放策略 * 小间距线性递增大间距几何递增 */ :root { /* 基础间距 Token — 桌面端基准 */ --spacing-0: 0; --spacing-1: 0.25rem; /* 4px */ --spacing-2: 0.5rem; /* 8px */ --spacing-3: 0.75rem; /* 12px */ --spacing-4: 1rem; /* 16px */ --spacing-5: 1.5rem; /* 24px */ --spacing-6: 2rem; /* 32px */ --spacing-8: 3rem; /* 48px */ --spacing-10: 4rem; /* 64px */ --spacing-12: 6rem; /* 96px */ /* 语义间距 Token — 映射到基础 Token */ --spacing-inline-xs: var(--spacing-1); /* 行内元素最小间距 */ --spacing-inline-sm: var(--spacing-2); /* 行内元素常规间距 */ --spacing-inline-md: var(--spacing-3); /* 行内元素宽松间距 */ --spacing-stack-xs: var(--spacing-2); /* 堆叠元素最小间距 */ --spacing-stack-sm: var(--spacing-4); /* 堆叠元素常规间距 */ --spacing-stack-md: var(--spacing-5); /* 堆叠元素宽松间距 */ --spacing-stack-lg: var(--spacing-6); /* 区块间距 */ --spacing-page-x: var(--spacing-4); /* 页面水平内边距 */ --spacing-page-y: var(--spacing-6); /* 页面垂直内边距 */ --spacing-card-padding: var(--spacing-4); /* 卡片内边距 */ --spacing-section-gap: var(--spacing-8); /* 章节间距 */ } /* 响应式间距适配 — 移动端缩小 */ media (max-width: 768px) { :root { --spacing-4: 0.75rem; /* 12px */ --spacing-5: 1rem; /* 16px */ --spacing-6: 1.5rem; /* 24px */ --spacing-8: 2rem; /* 32px */ --spacing-10: 3rem; /* 48px */ --spacing-12: 4rem; /* 64px */ --spacing-page-x: var(--spacing-3); --spacing-page-y: var(--spacing-4); --spacing-card-padding: var(--spacing-3); --spacing-section-gap: var(--spacing-6); } } /* 响应式间距适配 — 大屏放大 */ media (min-width: 1440px) { :root { --spacing-6: 2.5rem; /* 40px */ --spacing-8: 3.5rem; /* 56px */ --spacing-10: 5rem; /* 80px */ --spacing-page-x: var(--spacing-6); --spacing-section-gap: var(--spacing-10); } }/** * 间距 Token 校验工具 * 检查 CSS 中是否使用了非 Token 的间距值 */ interface SpacingViolation { file: string; line: number; property: string; value: string; suggestion: string; } class SpacingTokenValidator { private validSpacingValues: Setstring; constructor() { // 合法的间距值集合 this.validSpacingValues new Set([ 0, var(--spacing-0), 4px, var(--spacing-1), 8px, var(--spacing-2), 12px, var(--spacing-3), 16px, var(--spacing-4), 24px, var(--spacing-5), 32px, var(--spacing-6), 48px, var(--spacing-8), 64px, var(--spacing-10), 96px, var(--spacing-12), ]); } /** * 校验 CSS 文件中的间距值 * 检测非 Token 的硬编码间距值 */ validateCssFile(cssContent: string, filePath: string): SpacingViolation[] { const violations: SpacingViolation[] []; const spacingProperties [ margin, margin-top, margin-right, margin-bottom, margin-left, padding, padding-top, padding-right, padding-bottom, padding-left, gap, row-gap, column-gap, ]; const lines cssContent.split(\n); for (let i 0; i lines.length; i) { const line lines[i].trim(); for (const prop of spacingProperties) { const pattern new RegExp(${prop}:\\s*([^;]), i); const match line.match(pattern); if (match) { const value match[1].trim(); // 检查是否使用了 var() 引用 Token if (value.startsWith(var()) continue; // 检查是否是合法的硬编码值 if (!this.validSpacingValues.has(value) value ! auto) { violations.push({ file: filePath, line: i 1, property: prop, value, suggestion: this.findClosestToken(value), }); } } } } return violations; } /** * 找到最接近的 Token 值 * 将硬编码的间距值映射到最近的 Token */ private findClosestToken(value: string): string { const pxMatch value.match(/(\d(?:\.\d)?)px/); if (!pxMatch) return var(--spacing-4); const px parseFloat(pxMatch[1]); const tokenValues [0, 4, 8, 12, 16, 24, 32, 48, 64, 96]; let closest tokenValues[0]; let minDiff Math.abs(px - closest); for (const token of tokenValues) { const diff Math.abs(px - token); if (diff minDiff) { minDiff diff; closest token; } } const tokenIndex tokenValues.indexOf(closest); if (tokenIndex 0) return var(--spacing-0); return var(--spacing-${tokenIndex}); } }四、间距 Token 体系的工程权衡Token 数量、响应式策略与迁移成本Token 数量。Token 过多如 20 个增加选择困难Token 过少如 5 个无法覆盖所有场景。建议基础 Token 8-10 个语义 Token 5-8 个总计不超过 20 个。响应式策略。间距的响应式适配有两种策略修改基础 Token 值全局缩放和修改语义 Token 映射局部调整。全局缩放简单但可能影响不需要缩放的组件局部调整精确但维护成本高。建议混合使用基础 Token 全局缩放语义 Token 局部调整。迁移成本。将现有项目的硬编码间距迁移到 Token 体系需要逐个替换。自动化校验工具可以快速发现违规项但替换仍需人工确认。建议渐进式迁移新组件强制使用 Token旧组件在修改时逐步迁移。适用边界间距 Token 适用于中大型项目特别是多人协作的设计系统。对于小型项目或一次性页面Token 体系的维护成本可能超过收益。五、总结间距 Token 体系通过定义有限的间距变量约束间距值的选择范围从根本上解决间距不一致的问题。4px 网格是推荐的基础单位混合缩放策略在小间距线性递增、大间距几何递增。语义 Token如--spacing-stack-sm比基础 Token 更易理解和使用。响应式适配通过修改 Token 值实现无需修改组件代码。工程实践中建议使用校验工具检测非 Token 的硬编码间距渐进式迁移现有代码。