# Design System

> 前端设计的机械实现约束：Token 架构、字体层级、加载顺序、FOUT 防护、Chrome 稳定性、动效时序、颜色语义。在构建组件、页面或设计系统时与 design 配合使用。（美学方向由……决定）

- Skill: `kscz0000/design-system` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds@latest add kscz0000/design-system`
- Raw SKILL.md: https://api.skillmd.com/api/skills/kscz0000/design-system/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- License: MIT
- Author: kscz0000 (https://skillmd.com/u/kscz0000)
- Updated: 2026-09-21
- Page: https://skillmd.com/skills/kscz0000/design-system

---


# 设计系统
## 适用场景

在需要前端设计的机械实现约束时使用本技能：Token 架构、字体层级、加载顺序、FOUT 防护、Chrome 稳定性、动效时序、颜色语义。在构建组件、页面或设计系统时与 design 配合使用。（美学方向由……决定）


实现 UI（组件、页面或设计系统）时与 **design** 配合使用。每一种颜色、字体、动效选择都应可追溯到这些规则。

## Token 架构

所有颜色都映射到一组精简的基元值上。不允许随机的十六进制值。

- **前景色（Foreground）**：文本层级（主要、次要、弱化）。
- **背景色（Background）**：表面层级（基础、抬升、浮层）。
- **边框色（Border）**：分隔层级（细微、默认、强调）。
- **品牌色（Brand）**：身份与主要强调。
- **语义色（Semantic）**：危险、警告、成功（可选信息）。

在代码中使用 Token（CSS 变量、主题对象），UI 中绝不允许硬编码十六进制值。

## 字体排版

- **层级**：标题——更粗的字重、更紧凑的字距，体现存在感。正文——舒适的字重，便于阅读。标签/UI——中等字重，在较小字号下仍清晰。数据——等宽字体，配合 `tabular-nums` 保证对齐。
- 综合尺寸、字重、字距，让层级一目了然。如果眯起眼分不清标题与正文，说明层级太弱。
- **字体**：展示字体与正文字体搭配；保持层级一目了然。字体*选什么*属于方向问题，不属于机械规则——从领域出发，避开最初/默认的本能（即均值）；参见 design-spatial §2。
- **数据（仅功能性）**：真实对齐的数字、ID、时间戳使用等宽字体配合 `tabular-nums`——等宽只有在数值按列对齐时才真正发挥作用。不要在装饰性小标题/元信息微文本（例如 "35MM · DEVELOP · SCAN"、伪规格说明）上随手点缀等宽字体来营造"技术感"——那是当下流行的套路，而不是数据本身。参见 design-spatial §2。

## 加载顺序——先看见的先加载

首屏必须在快速完成绘制的同时保证完整、正确。每一项资源都要按用户是否最先看见来排序；其余的稍后排队。

- **只优先处理首屏资源**（标题文本、主图/视频、品牌标识）。全部预加载等于什么都没预加载——真正关键的资源会在带宽竞争中落败。挑选首屏中真正会用到的少数几个，优先加载它们。
- **字体：自托管 WOFF2。** 将 OTF/TTF 转为 WOFF2（Brotli；体积约为一半，字形一致），并通过 `<link rel="preload" as="font" type="font/woff2" crossorigin>` 预加载首屏使用的字重。绝不要使用阻塞渲染的第三方字体样式表——Google Fonts 的 `<link>` 会增加一次 CSS 往返和额外的 DNS/TLS，字体还没开始下载；改为自托管。
- **LCP 图片/视频：** 在主图（或视频封面）上加 `fetchpriority="high"`；当它通过 CSS 引用时使用 `<link rel="preload" as="image">`（解析器无法提前看到 CSS 的 `url()`）。首屏位置绝不能是空的——提供一个封面/低分辨率占位，避免出现空白帧。
- **首屏以下：** 图片使用 `loading="lazy" decoding="async"`；视频使用 `preload="none"`（或 `"metadata"`）；非关键 JS 加 `defer`。始终通过 `aspect-ratio` 或 `width`+`height` 预留空间，避免延迟加载的媒体引起布局偏移（CLS）。
- 保持阻塞渲染的 head 最小：内联关键 CSS，其余延迟加载。

## 绝不让字体突然出现（杜绝 FOUT）

`font-display: swap` **本身就是**那个"突然出现"——它先画一个兜底字体，再替换为网络字体并触发重排。对任何会被用户看着加载的文本（标题、文字标志、主标语），都不要使用。规则是绝对的：标题/展示文本绝不能闪现兜底字体或发生重排。

- **以真实字体就绪作为可见性门控。** 同步地在 `<head>` 中为 `<html>` 添加 `fonts-pending` 类，把展示字体文本的 `opacity` 设为 `0`。在 `document.fonts.ready` 时——通过 `document.fonts.load('<weight> 1em "Family"')` 为每个关键字体显式触发——切换到 `fonts-ready` 类并淡入文本（约 0.5 秒）。始终包含一个安全超时（约 2.5 秒），无论是否就绪都强制显示，避免字体加载失败导致文本永久不可见。
- 与上文 preload + WOFF2 配合使用，使隐藏窗口只有几百毫秒，而不是几秒——这种淡入看起来是有意为之，而不是卡顿。
- 对于正文中可以容忍极轻微替换的情况，至少要消除重排：通过带 `size-adjust` / `ascent-override` / `descent-override` 的兜底 `@font-face`（或 `font-family` 兜底列表）让兜底字体占据与网络字体相同的字形指标，使替换不产生位移。

实操示例——一个 AR 产品调研页面：head 中的脚本切换 `fonts-pending → fonts-ready`（标题在 `fonts.ready` 时淡入，2.5 秒兜底），预加载首屏四种字重的 WOFF2，自托管品牌字体以避免 Google 的额外往返。

## 慢加载内容——绝不要展示难看的中间态

任何*可能*需要稍长时间才能就绪的内容——字体（如上所述）、大图、视频、`<canvas>` 场景、Three.js / WebGL、懒加载的 React 岛块、任何通过网络获取或执行重的主线程初始化的内容——必须做到**快速就绪**或**优雅加载**。浏览器默认行为（空白框 → 部分绘制 → 重排 → 最终态）就是那个难看的中间态。必须拦截它。

两个杠杆；同时使用：

- **加速就绪。** 压缩（字体用 WOFF2、glTF 用 Draco、图片用 WebP/AVIF、视频用 h264/h265 加 `preload="metadata"`）。预加载首屏真正需要的*少数*资产（`<link rel="preload">`）。首屏以下的内容懒加载，避免 LCP 资源争夺带宽。通过 `aspect-ratio`、`width`+`height` 预留容器，避免延迟内容引发 CLS。
- **优雅加载。** 用风格统一的占位元素隐藏加载中的状态，待真实内容就绪后再淡入。骨架屏、低分辨率模糊封面、单个 ASCII 字符，甚至是容器的背景色——任何与设计语言一致的内容都比默认的部分绘制更好。

"难看"具体长什么样，以及对应修复：

| 症状 | 修复 |
|---|---|
| 标注标签在 JS 定位前都堆在 `translate(0,0)`（容器左上角） | 标签初始 `opacity: 0`，配合 `transition: opacity ~0.35s`；首次投影时设置内联 opacity → 由 CSS 完成淡入。 |
| Canvas/WebGL 首帧渲染为空/黑色 | 在同一容器中先显示占位（CSS 图形、低分辨率封面图，或纸张/骨架填充）；待真实首帧渲染完成后移除。 |
| 懒加载图片请求完成时直接出现并引发布局跳动 | `aspect-ratio` + `<link rel="preload">`（首屏）或 `loading="lazy" decoding="async"`（首屏以下）；首屏渲染时从 `opacity:0` 在 `load` 事件上淡入。 |
| 视频封面在播放时突变为第一帧 | `poster` 与你能控制的静帧一致；`playing` 事件触发时，过渡已经干净完成。 |
| 3D 模型毫无过渡地"出现"在屏幕中央 | 让画布保持可见但 `opacity: 0`；在 GLTFLoader 成功回调中、首次 `tick()` 之后，切换 `.viewer-ready` 类（或直接设置内联 opacity）。 |
| 懒加载 React 岛闪现比"无 UI"还难看的兜底 | 将 `Suspense` 兜底替换为与最终布局一致的骨架，而不是 spinner。 |

经验法则：如果用户在加载途中截屏，你会觉得丢人，那就有责任为它设计一个优雅状态。占位不必花哨——它必须*有意为之*，尺寸正确，且与即将到来的内容使用相同的设计语言。

## Chrome 保持稳定——状态文字不能引发布局变化

持久化的 Chrome（顶栏、导航、工具栏、搜索栏、状态区）必须保持**恒定高度**，无论其中出现什么文本。临时状态/加载/说明文案——"loading model…"、"N matching · M indexed"、空状态提示——不允许换到第二行把相邻控件挤下去。一个随消息长度伸缩的状态区是布局抖动 bug，不是动态内容。

- **约束为单行：** 使用 `white-space: nowrap; overflow: hidden; text-overflow: ellipsis`，让最长消息截断而非换行。
- **提前预留空间：** 给容器一个固定的 `height`（或 `min-height`），按消息长度设定，使最短和最长状态——以及空状态——占据相同的空间。

只有内容区可以移动，Chrome 必须保持稳定；由临时文本引起的布局偏移读起来就像粗糙的故障。（这条规则避免的具体故障：在搜索应用中，模型加载消息换行成两行把搜索栏挤下去。）

## 动效

- 保持时序统一且有目的；一个精心编排的瞬间（带 `animation-delay` 的交错加载）胜过零散的微交互。HTML 优先用纯 CSS；React 使用 Motion 库。（面向公开/多用户项目时尊重 `prefers-reduced-motion`。）
- **克制/专业 UI 的默认值**（作为起点，不是定律）：微交互约 150 毫秒，较长的过渡 200–250 毫秒，缓出。俏皮/玩具般的基调（design-thinking）可能需要 spring/bounce 和更长节拍——让动效手感匹配所选方向，而不是默认使用这些数字。
- **编排**——对于超出单个微交互的内容（路由/页面过渡、列表重排、揭示、共享元素），加载 [references/motion-choreography.md](references/motion-choreography.md)：何时一个过渡值得保留（必须*传达*信息，否则删掉）、实现哪些种类以及顺序、**按导航类型选择风格**（仅在层级/有序场景使用定向滑动——同级之间使用滑动是错误的层级暗示；横向切换用淡入）、时长表，以及工艺细节（仅限合成器属性、变形时的运动模糊、绝不对文本做栅格缩放、持久 Chrome 隔离）。框架无关。

### 滚动叙事（scrollytelling）

对于**解释性/编辑性/数据讲解类**内容，优先选择**滚动驱动图形而不是点击交互式控件**。读者默认会滚动；让他们为推进一段说明而点击切换会增加摩擦并被跳过。使用 NYT/Pudding 模式：固定一张图（`position: sticky`），让短文本"步骤"从它旁边滚过，由每个步骤驱动图形状态。

- **机制：** 一个 `IntersectionObserver`，配合 `rootMargin: '-48% 0px -48% 0px'`（threshold 0），让一个步骤恰好在穿过视口中线时变为"激活"；激活索引重新渲染被固定的图形。约 30 行——这就是去依赖的 scrollama；不要引入滚动库。
- **布局：** 两列——一列是滚动的步骤，另一列是 `sticky top-0 h-screen` 的图形；移动端堆叠时图形在顶部固定。每一步约 85vh，使每屏恰好居中一个；非激活步骤卡片调暗（`opacity:.3`），让当前步骤突出。
- **图形是激活步骤的纯函数**（`graphic(active)`），自身不持有任何点击状态——因此它能确定性截图/导出，并可降级为静态图。在状态之间添加动画（颜色/宽度/透明度，300–700 毫秒），让滚动连续而不是跳跃。
- **不适用的场景：** 仪表盘、工具、表单——任何用户*操作*而不是*阅读*的内容——保持可交互。Scrollytelling 用于**叙事**，由你掌控节奏。（面向公开/多用户构建：根据上面的动效说明尊重 `prefers-reduced-motion`；保留状态变化但去掉补间。）

## 空间构图与布局

栅格系统、8 点间距标尺、视觉权重平衡、对齐以及"渲染—批评"循环位于 **design-spatial** ([../design-spatial/SKILL.md](../design-spatial/SKILL.md))——是本文件 Token/字体/颜色规则的机械对应物。在编排页面、仪表盘或组件时随时加载它。（属于本节的方向要点：构图野心要匹配愿景——极繁主义配得上繁复/分层的代码；极简/精致要求克制与精确的间距。）

## 嵌套圆角（仅当一个圆角元素嵌套在另一个圆角元素内时适用）

不鼓励把东西都变圆——本规则只处理圆角元素嵌套在另一个圆角元素中的情况（卡片里的按钮、容器中的内嵌面板）。嵌套时：

- **子元素圆角 ≤ 父元素圆角**，绝不能更大（子元素比父元素更圆，会显得从里面鼓出来）。
- **同心圆**为理想情况：`子圆角 = 父圆角 − 间隙`（两者之间的内边距），让两条曲线平行，内圆角呼应外圆角。在圆角父容器内放平直/无圆角的子元素没问题；真正显得别扭的是不匹配、非同心的曲线。

## 颜色

- **调色板来自领域**：颜色应当感觉*来自*产品所处的世界，而不是贴在表面。
- **不止冷暖**：安静与喧闹、密集与舒展、严肃与俏皮、几何与有机——不仅仅是暖色/冷色。
- **颜色承载含义**：灰色搭建结构；颜色传达状态、动作、强调、身份。毫无动机的颜色就是噪音。（克制——一个强调色，而不是五个——是方向性原则；参见 design-thinking → *把冲击力留给标点*。）
- **对比度——APCA 决策，WCAG 守门。** 对于*感知性*的对比度判断（这段文字在这个底色上是否舒适易读？），优先使用 **APCA**（[apcacontrast.com](https://apcacontrast.com/)）——它对明度感知的建模远胜于 WCAG 2 的比例，WCAG 在浅底深字和中调场景下经常误判。把 **WCAG 2（4.5 / 3:1）** 留作合规底线——它是 `design-spatial` 中 `layout-audit.js` 的门禁，也是无障碍标准的硬要求。用 APCA 来设计，用 WCAG 来认证。
- **交互态增强对比度。** `:hover`、`:active`、`:focus` 必须比静态态更*突出*——更高对比度，而不是更低。让 hover 反而降低对比度（例如把文字颜色往背景色方向调浅）读起来像是禁用态。

避免通用/趋势化外观（Inter、紫底白字、千篇一律的深色玻璃卡片），并在代际之间做出变化属于 **design-spatial §2** 的内容——不在此重复。

## 背景与细节

氛围胜过平涂——但要匹配所选的美学，而不是默认套路。条件反射式地使用渐变网格/噪点/颗粒来营造"高级感"，本身就是设计师趋势的均值（design-spatial §2）；只在方向真正需要时才使用，绝不为装饰而装饰。

## 局限

- 仅当任务明确匹配其上游来源和本地项目上下文时使用本技能。
- 在应用变更前，验证命令、生成代码、依赖、凭据以及外部服务行为。
- 不要把示例当作环境特定测试、安全审查或破坏性/高成本操作的用户批准的替代品。
