设计系统中的间距 Token 体系:从 8px 网格到响应式间距的工程实践
2026/6/12 12:11:00 网站建设 项目流程

设计系统中的间距 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 --> O

8px 网格系统的核心思想:所有间距值都是 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: Set<string>; 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 的硬编码间距,渐进式迁移现有代码。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询