# Stash UI 设计系统 · 规格总纲 > 版本 0.1 · 适用 `@stash/ui` > 本文件定义**所有组件共同遵守的规则**。新增组件、改动组件、评审组件都以下面这些条目为准。 四份文档的分工: | 文档 | 回答什么问题 | 什么时候看 | | --- | --- | --- | | **DESIGN.md**(本文) | 整体结构与硬性规则:分层、令牌、视觉、代码约定、验收标准 | 动手前看,评审时逐条对照 | | [docs/COMPONENTS.md](docs/COMPONENTS.md) | 有哪些组件、各自的契约、缺什么组件、缺的时候该做什么 | 想知道「用哪个 / 有没有」时看 | | [docs/ADDING-A-COMPONENT.md](docs/ADDING-A-COMPONENT.md) | 从零加一个组件的完整流程、模板、示例 | 确认要新增时照着做 | | [docs/ALRIGHT-DECOMPOSITION.md](docs/ALRIGHT-DECOMPOSITION.md) | 参考站拆解:实测令牌、组件尺寸、交互节奏、暗色映射、保留差异 | 怀疑「这个值对不对」时查依据 | 展示站(`site/`)是这三份文档的运行实例:**文档里写的规则,展示页上必须能直接看到**。 --- ## 1. 三条硬约束 违反任意一条,组件不合并。 1. **只认令牌** 组件 CSS 里的颜色、字号、行高、圆角、阴影、动效、间距,一律 `var(--su-*)`。 允许的字面量只有四类:`0`、`1px` 边框、`100%`、以及组件内声明的局部尺寸变量 `--xxx-size`(见 §5.3)。 2. **组件不认识业务** 不请求接口、不读路由、不 import 业务类型(`Category` / `entry` 之类)。 组件只接收 props,数据由页面喂给它。 3. **每个组件都有展示页** 展示页只能用真实组件渲染,不能写「演示专用版本」;新增组件必须同时加一节。 --- ## 2. 分层结构 | 层 | 职责 | 现有组件 | 硬性规则 | | --- | --- | --- | --- | | **L1 原子** | 不可再分的小元素 | `Icon` `Badge` `Tag` `Divider` | 不负责布局,**不带外边距**,尺寸由内容或父级决定 | | **L2 控件** | 接收输入 / 触发操作 | `Button` `IconButton` `Input` | 高度只用 `--su-control-height-*` 或 `--su-icon-button-*` | | **L3 容器与布局** | 承载内容、排布 | `Card` `Grid` `Space` | 不假设内容形态;不写死宽度;不感知业务 | | **L4 反馈** | 表达状态 | `Notice` `Empty` `ScrollTop` | 不阻塞、不弹层 | | **页面级(不进库)** | 顶栏、侧栏、详情页、业务卡片 | — | 放在应用里,用 L1–L4 拼装 | 依赖方向: ```text L4 ──→ L3 ──→ L1 │ │ └──────┴──→ L1 L2 ──→ L1 ``` - 只允许「上层依赖下层」,**禁止下层 import 上层**(例如 `Card` 里不 import `Empty`)。 - 组件之间不共享 CSS。除 `Card` 这种明确的容器外,组件不假设自己处在什么容器里。 - 业务组件(分区卡片、应用条目、海报卡……)属于页面层:**用库里组件拼,不进库**。 --- ## 3. 令牌(唯一的视觉来源) 完整定义见 [`src/theme/tokens.css`](src/theme/tokens.css)。前缀 `--su-` 是为了避免和宿主应用变量冲突。 所有取值都来自参考站实测(见 [docs/ALRIGHT-DECOMPOSITION.md](docs/ALRIGHT-DECOMPOSITION.md)),不是拍脑袋定的。 ### 3.1 颜色 | 令牌组 | 用途 | 不要用于 | | --- | --- | --- | | `--su-gray-1..8` | 灰阶底座(与参考站一致) | 直接当品牌色用 | | `--su-bg-page` / `--su-bg-card` / `--su-bg-hover` / `--su-bg-subtle` / `--su-bg-light` | 页面底、卡片底、悬浮底、浅色块(标签)、浅色块(按钮) | 做强调色 | | `--su-bg-notice` / `--su-bg-danger-soft` | 提示条底色 | 卡片底色 | | `--su-text-main` / `-secondary` / `-sub` / `-muted` / `-placeholder` | 文字层级(主文 → 次级 → 说明 → 占位) | 互相替代 | | `--su-border` / `--su-border-strong` / `--su-card-border` | 默认细边 / 需要看清的分隔 / 卡片描边(几乎不可见) | 画装饰线 | | `--su-color-primary` (+`-hover` `-soft`) | 主操作、链接、选中 | 大块背景 | | `--su-color-danger` (+`-hover` `-soft` `-border`) | 危险、错误 | 普通强调 | | `--su-color-warning` (+`-soft` `-text`) / `--su-color-orange-dark` `-light` | 提醒、付费角标 | 错误态 | | `--su-color-success` (+`-soft`) | 在线、成功 | 主操作 | | `--su-gradient-verified` / `-hot` / `-paid` / `-red` | 认证角标 / 置顶 / 付费 / 热门 | 大面积背景 | ### 3.2 间距 `--su-space-1..10` = 4 / 8 / 12 / 16 / 20 / 24 / 32 / 40。 只用这些值;需要 2px、6px 这类视觉微调时才允许字面量(且仅限内边距/圆点尺寸)。 ### 3.3 字号与字重 | 令牌 | 值 | 用途 | | --- | --- | --- | | `--su-font-size-xs` / `--su-line-height-xs` | 12 / 16 | 角标(`Badge` / `ItemBadge`) | | `--su-font-size-sm` / `--su-line-height-sm` | 13 / 20 | 标签、辅助文字 | | `--su-font-size-md` / `--su-line-height-md` | 14 / 22 | 控件文字(按钮、输入、表格) | | `--su-font-size-base` / `--su-line-height-base` | 16 / 24 | 内容正文、卡片标题(`Paragraph` / `Title level=3`) | | `--su-font-size-lg` / `--su-line-height-lg` | 18 / 26 | 区块标题、大图标 | | `--su-font-size-xl` / `--su-line-height-xl` | 24 / 32 | 页面标题(`Title level=1`) | > 参考站的实际排版是 `13/20`、`14/22`、`16/24`、`18/26`、body `16/24`。 > 组件库把「控件文字」定在 14、把「内容正文」定在 16:控件更紧凑,文章更好读。 字重只有三档:`normal 400`、`bold 500`(默认「加粗」)、`strong 700` (**只给两类**:字形符号如认证角标的 ✓,以及 12px 的 `ItemBadge` 状态角标)。 组件内**禁止**写 `font-weight: 600`。 ### 3.4 圆角、阴影、控件尺寸、动效 | 令牌 | 值 | 用途 | | --- | --- | --- | | `--su-radius-xs` / `sm` / `md` / `lg` / `pill` | 4 / 6 / 12 / 24 / 999 | 标签 / 控件 / 卡片 / 大容器 / 胶囊 | | `--su-shadow-card` | `0 6px 16px 0 rgb(27 26 49 / 1%)` | 卡片静止(参考站实测,极轻) | | `--su-shadow-hover` | 同位置参数,3% | 卡片悬停 | | `--su-shadow-pop` | `0 8px 24px rgb(27 26 49 / 12%)` | 浮层 | | `--su-control-height-sm` / `md` | 32 / 36 | 按钮、输入框、未来的 Select | | `--su-icon-button-xs` / `sm` / `md` | 28 / 36 / 44 | 圆形图标按钮(参考站 `.btn-icon`) | | `--su-duration-fast` / `base` | 0.2s / 0.3s | 颜色变化 / 展开淡入 | | `--su-ease-in-out` / `-out` / `-in` | ease-in-out / ease-out / ease-in | 默认用 `-in-out`(参考站同款) | | `--su-skeleton-shine` | 浅色 50% 白 / 深色 8% 白 | 骨架屏扫光 | | `--su-focus-ring` / `-offset` | `2px solid primary` / 2px | 键盘焦点环 | ### 3.5 布局与阅读 | 令牌 | 值 | 用途 | | --- | --- | --- | | `--su-layout-width` | 1380px | 页面容器最大宽度(Layout `contained` 与页面共用) | | `--su-header-height` | 60px | 应用型页头高度 | | `--su-radius-cover` | 8px | 封面卡圆角(比卡片小一档) | | `--su-reading-line-height` | 1.9 | 长文正文行高(`Paragraph leading="relaxed"`) | | `--su-cover-scrim` | 黑 85% → 40% → 透明 | 封面标题的压暗层 | | `--su-shadow-card-strong` | 2% 卡片阴影 | 需要更明显层次时(如首页分区卡) | | `--su-z-header` / `--su-z-float` | 100 / 90 | 顶栏 / 浮动元素 | --- ## 4. 视觉规则(拿不准就按这里来) ### 4.1 尺寸与间距 - 控件高度只有两档;**并排出现时必须同档**(`Input` 与 `Button size="md"` 都是 36px)。 - 组件**不带 margin**;元素间距交给 `Space` 或父容器。例外:`ScrollTop` 是浮动元素、`Divider` 自身带间距。 - 卡片内边距:`sm 12` / `md 20` / `lg 24`,不要出现 18、22 这类值。 ### 4.2 颜色的用法 - 文字三档:主文 `text-main`、次级 `text-sub`、说明 `text-muted`;不要自造灰。 - **行式条目(列表行、作者行)不加底色、不加边框、不加阴影**——这是主题的核心观感,`Card` 不要套在行上。 - 卡片用「1px 细边 + 极轻阴影」,不要用重阴影或彩色描边制造层级。 ### 4.3 状态矩阵 每个交互组件都要覆盖下表;展示页也要逐个状态摆出来。 | 状态 | 实现 | 备注 | | --- | --- | --- | | 默认 | 令牌默认值 | — | | hover | 主色/危险按钮 `opacity: .8`;次要按钮换 `gray-3` 底;图标按钮换 `gray-2` 底 + 文字加深 | 参考站同款,不做位移、不缩放 | | active | 与 hover 同色,不再额外变形 | 主题本身没有按压态 | | disabled | 换浅底 + 弱文字 + `cursor: not-allowed`(不是整体降透明) | 不改变布局 | | loading | 同色 spinner 占用图标位 + 自动 disabled + `aria-busy` | 仅 `Button` / `IconButton` | | focus-visible | 组件自己写 `outline: var(--su-focus-ring)` + `offset` | 参考站把焦点样式删了,我们**必须**补(WCAG) | | 空 | `Empty` | 不复用 `Notice` | | 错误 | `Notice tone="error"` | 带 `role="alert"` | ### 4.4 动效 - 过渡时长统一用 `--su-duration-base`(0.3s)+ `--su-ease-in-out`,与参考站一致; 只有 hover 的快速反馈可以用 `--su-duration-fast`(0.2s)。 - 只做颜色 / 背景 / 边框 / 阴影 / 透明度,不做位移、缩放、旋转(加载 spinner 除外)。 - 需要 `transition` 的:`Button`、`IconButton`、`Card hoverable`、`Input`、`ScrollTop`、`Tag`(如加交互)。 - **每个带动效的组件都要有 `@media (prefers-reduced-motion: reduce)` 分支**(过渡置 none,spinner 放慢)。 ### 4.5 响应式 断点只有三个档,**组件内部**处理,页面不要各写一套: | 断点 | 谁在用 | 行为 | | --- | --- | --- | | `1200px` | `Layout` | 右栏(aside)从并排变成堆到内容下方 | | `1100px` | `Layout`、`Grid` | 侧栏堆到内容上方;`Grid` 降为 2 列 | | `820px` | 页面(如示例页) | 顶部导航收进抽屉、左侧资料栏隐藏 | | `720px` | `Grid` | 降为 1 列 | - 组件宽度默认 `100%`,由容器决定;不写死像素宽度(演示代码除外)。 - **不靠隐藏做适配**:`Layout` 在窄屏下把侧栏/右栏堆叠,内容一律保留;页面如果确实要隐藏(如移动端的资料栏),由页面自己决定。 - 高度用 `100dvh` 优先(`100vh` 兜底),地址栏收起时不会跳动。 - 弹层宽度必须夹住:Modal `calc(100vw - 32px)`、Popover `min(320px, calc(100vw - 32px))`、Drawer `max-width: 100vw`。 - 表格/标签栏/分段控件在窄屏横向滚动,不做压缩截断。 ### 4.6 触屏 - 交互只用 `click`,不依赖 hover;`Tooltip` 的 hover 样式包在 `@media (hover: hover)` 里,触屏走 `:focus-within`。 - 可点区域:`xs` 图标按钮 28×28 是**桌面密集场景**下限;移动端布局请用 `sm(36)` / `md(44)`。 - 按钮类加 `touch-action: manipulation`,去掉双击缩放带来的点击延迟。 - 原生控件(checkbox/radio/switch)都包在 `