关于 components
components 是用于构建一致且易访问的界面组件的设计技能。它涵盖组件 API、交互状态、样式和测试,并建议复杂行为采用无头原语实现。
主要功能
- 01为复杂交互选择原语
- 02定义完整的交互状态
- 03组合受控与非受控 API
- 04使用设计令牌设置样式
- 05测试键盘、响应式和辅助技术使用
适用场景
- 01规划易访问的对话框或菜单
- 02检查组件状态覆盖情况
- 03统一组件 API 与样式
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ paragonhq/ stealth-skills --skill components安装后在 Claude Code 中输入/components确认已加载。
详细介绍
组件
组件是一种契约:无论在哪里看起来都一样、表现都一样,而且无需检查就值得信任。大多数生成的组件都无法做到第三点。
使用基础组件还是手工实现?
只有没有行为的组件才手工实现:卡片、徽章、分隔线、统计数据。任何涉及焦点管理、键盘交互、定位或 ARIA 的组件都使用无头基础组件。
| 需求 | 基础组件 | 不手工实现的原因 |
|---|---|---|
| 对话框、侧边面板、弹出框、菜单、选择器、工具提示、标签页、切换开关、滑块、组合框 | Base UI、Radix、React Aria、Ark | 焦点陷阱、Escape、外部点击、按键搜索、ARIA、portal、碰撞检测 |
| 可拖动抽屉 | Vaul | 手势物理效果、吸附点、滚动锁定 |
| Toast | Sonner | 堆叠、滑动、计时器、无障碍通知 |
| 命令面板 | cmdk | 过滤、键盘操作、分组、无障碍支持 |
| 动画数字 | NumberFlow | 数字列、本地化格式 |
| 数据表格 | TanStack Table(无头) | 排序、虚拟化、列状态 |
| 表单 | 原生功能 + 小型验证库 | 约束 API、将焦点移至首个错误 |
规则:如果 ARIA Authoring Practices 中有对应规范,就使用实现了该规范的基础组件。
状态矩阵
每个交互组件完成前都必须涵盖以下所有状态。写下来,并逐项核对。
| 状态 | 视觉效果 | 行为 |
|---|---|---|
| 默认 | — | — |
| 悬停(仅限悬停设备) | 单级变化 | — |
| 焦点可见 | 焦点环 | — |
| 激活 / 按下 | 缩放至 .97 或变暗 | — |
| 已选中 / 已勾选 / 已开启 | 填充或强调色 | aria-* 反映该状态 |
| 已禁用 | 50% 不透明度,不响应悬停 | 不可聚焦;如果必须保留在 Tab 顺序中,则设置 aria-disabled |
| 忙碌 / 加载中 | 原位显示微调器,宽度不变 | 忽略输入,设置 aria-busy |
| 错误 / 无效 | 边框 + 提示信息 | 设置 aria-invalid,并通过 aria-describedby 指向提示信息 |
| 空(用于容器) | 一行说明 + 一个操作 | — |
| 只读 | 文本样式,无操作提示 | — |
尺寸:两种或三种(sm、md、lg)。变体:primary、secondary(边框)、ghost、danger。不要七种,也不要 outline-gradient-xl。
根据状态设置样式,而非根据 props
样式读取元素的实际状态,使组件与其 CSS 不会出现不一致:
<button data-state={open ? "open" : "closed"} data-size={size} data-variant={variant} aria-busy={busy || undefined} />
.button[data-variant="primary"] { ... }
.button[data-state="open"] { ... }
.button[aria-busy] { ... }
.button:where(:disabled, [aria-disabled="true"]) { ... }
基础组件已经会提供 data-state、data-highlighted、data-disabled;直接基于这些属性设置样式,不要将它们复制到 React 状态中。
通过组合设计 API
由小部件组合而成,胜过一个拥有四十个 props 的组件:
<Dialog.Root>
<Dialog.Trigger asChild><Button>Rename</Button></Dialog.Trigger>
<Dialog.Content>
<Dialog.Title>Rename file</Dialog.Title>
<Dialog.Description>…</Dialog.Description>
<Dialog.Close asChild><Button variant="secondary">Cancel</Button></Dialog.Close>
</Dialog.Content>
</Dialog.Root>
指南:
- 使用
asChild/render,将行为添加到用户的元素上,而不是再包一层。 - 将其余 props 展开到根元素上;透传
className和style;转发 ref。 - 状态使用布尔型 props,变体使用字符串联合类型;不要让
type="..."与原生属性冲突。 - 受控和非受控模式都要可用:使用
value+onValueChange,或defaultValue。绝不能只实现其中一半。 - 事件处理器命名为
onX,接收原生事件或普通值,绝不接收自行发明的合成对象。 - 不要同时使用
isLoading/loading;统一使用一个名称,并保持一致。
使用 token,而非具体值
组件通过 token 引用颜色、圆角、间距、缓动和持续时间。如果组件中内联写有十六进制颜色、0.3s 或 cubic-bezier,就意味着它偏离了系统。
每一层的圆角保持一致:输入框和按钮为 6–8px,卡片和弹出框为 8–12px,对话框为 12–16px,药丸形元素使用全圆角。嵌套圆角逐层缩小:12px 卡片内的元素使用 8px,而不是 12px。
定位层级
弹出框、菜单和工具提示:使用 portal 挂载到 body,根据触发器定位并处理碰撞,将 transform-origin 设置为最终定位方向的起点,并以约 160ms 的缩放 / 透明度动画进入。对话框:居中显示,带背景遮罩,锁定滚动且不造成布局偏移(补偿滚动条宽度),打开时将焦点移入,关闭时将焦点移回。
Z-index 应使用层级刻度,而不是随意的数值:--z-sticky: 10; --z-dropdown: 20; --z-dialog: 30; --z-toast: 40。任何高于 50 的值都是错误。
输入框
- 显示可见标签。占位符是提示,不是标签。
- 错误消息显示在下方,并通过
aria-describedby关联;在失焦或提交后显示,而不是每次按键都显示。 - 高度与相同尺寸的按钮一致(
sm为 32px,md为 36px,lg为 40px)。 - 触屏设备上使用 16px 字号,否则浏览器会缩放页面。
- 设置
inputmode和autocomplete。手机上设置enterkeyhint。 - 输入框内的图标属于装饰元素,除非它们是按钮;即使按钮绘制为 16px,点击区域也应为 32px。
列表和行
- 整行都是目标区域(在链接上使用覆盖整行的
::after伪元素),而不只是名称。 - 只用一个悬停高亮,并随指针移动,不要为每行单独设置背景。
- 键盘操作:按
j/k或方向键移动,按Enter激活,聚焦的行要有清晰可见的轮廓。 - 截断描述,不要截断名称。
- 超过约 200 行时使用虚拟化。
图标
统一使用一套图标、一种描边宽度。在同一场景中统一尺寸(密集 UI 使用 14px,舒适场景使用 16px,触屏场景使用 20px)。使用 currentColor,使图标颜色与文本一致。装饰性图标使用 aria-hidden;仅含图标的按钮使用 aria-label。
表格
- 表头行固定,并带有底部边框。
- 数字右对齐并使用等宽数字;文本左对齐;不要居中。
- 行高为 32–40px;仅在非常密集的表格中使用斑马纹,并采用
--hover级别。 - 在表头中显示排序指示器;无需悬停即可看出当前活动列。
- 空状态应显示在表格区域内,而不是替代表格区域。
测试组件
在认为组件完成之前,打开它并:
- 拔掉鼠标,用 Tab 键遍历组件。
- 按下、按住、拖离,然后松开。
- 将窗口调整为 375px 宽。
- 切换深色和浅色模式。
- 开启减少动态效果设置。
- 分别填入最长的合理内容和最短的内容。
- 使用屏幕阅读器检查:组件是否有名称、角色和状态?
生成式组件的特征
| 特征 | 修复方法 |
|---|---|
使用 useState 和外部点击钩子的自定义下拉菜单 | 使用菜单基础组件 |
variant 有八种取值 | 减为四种 |
未转发 className | 转发并合并 |
内联使用 transition: all 0.3s | 使用设计令牌 |
| 加载指示器替代标签,且按钮缩小 | 将加载指示器覆盖显示,并固定宽度 |
| 仅含图标且没有标签的按钮 | 使用 aria-label |
div onClick | 改用 button |
| 没有焦点陷阱的模态框 | 使用对话框基础组件 |
目录中的资源
- Base UI、Radix Primitives、React Aria、Ark UI —— 无头行为组件,已处理好无障碍功能。
- shadcn/ui —— 基于基础组件提供完整外观,并复制到你的代码库中。
- Sonner、Vaul、cmdk、NumberFlow —— 各自解决特定问题。
- ARIA Authoring Practices —— 每种模式的键盘操作和角色规范。
- 更多内容请访问 stealth.pm/components。