Ark UI 组件示例创建实战:从 API 分析到跨框架 Storybook 与文档同步的完整工作流
Ark UI 组件示例创建实战:从 API 分析到跨框架 Storybook 与文档同步的完整工作流
Ark UI 是一套基于 Zag.js 状态机构建的无样式(headless)、可访问组件库,同时覆盖 React、Solid、Svelte 与 Vue 四个框架。本指南以仓库内的 component-example-creator 智能体规范(.claude/agents/component-example-creator.md)为骨架,完整讲解为组件编写示例的标准流程:如何分析组件 API、设计示例集、在四个框架中平行实现、更新 Storybook stories、同步官网文档,以及如何做质量验证。读完本文,你将掌握一套可复制的、让组件示例“一次编写、四框架齐出、Storybook 与文档自动同步”的工程化方法。
角色定位:组件示例创建者的三项核心职责
在 Ark UI 的开发体系中,为组件产出示例并非简单的“写个 demo”,而是一套与仓库工程结构深度绑定的协作规范。根据组件示例创建者的职责定义,其核心工作有三块:
- 创建组件示例:开发实用、贴近真实场景的示例,覆盖组件的功能特性、变体与使用场景,且必须横跨全部四个受支持框架(React、Solid、Svelte、Vue)。
- 更新 Storybook Stories:维护并增强
*.stories.tsx/*.stories.ts/*.stories.vue文件,保证示例组织有序、可访问,并体现最佳实践。 - 同步文档:让组件文档中的代码示例、stories 与文档保持三方一致。
触发这类工作的典型场景包括:用户为 Checkbox 请求不同状态的示例、为 Select 新增 clearable 属性后需要配套示例、Avatar 的 stories 缺少新的 size 变体等。本质上是"功能实现 → 示例演示 → 文档沉淀"链条中的示例环节。
创建示例的 7 步实现工作流
规范中有一条硬性约束:每个示例必须走完下述所有步骤,跳过任何一步都视为示例不完整。这也是把"示例"从临时 demo 升级为仓库一等公民的关键。
第 1 步:分析组件 API
先阅读组件的 TypeScript 类型、props 接口和现有实现,摸清所有可用特性与变体。以仓库中的 Checkbox 为例,其根组件基于 use-checkbox.ts 的 useCheckbox 钩子,通过 @zag-js/checkbox 的 machine 与 connect 串联状态机,props 会与 Field、CheckboxGroup 的上下文自动合并(mergeProps(checkboxGroup?.getItemProps(...) ?? {}, ownProps))。这意味着 API 分析不能只看组件表面,还要理解它依附的上下文:disabled、readOnly、invalid、required 等布尔属性会从 useFieldContext 自动透传。
第 2 步:审查现有示例
检查当前 stories 与文档,找出缺口、过时模式或缺失的使用场景。每个组件目录下有独立的 examples/ 文件夹,可直接对比四框架的文件清单是否对齐。例如 React Checkbox 目录 的 examples/ 下有 basic、controlled、default-checked、disabled、group、indeterminate、root-provider、with-field、with-form 等 17 个示例文件,Vue 与 Svelte 侧也有同名等价文件——审查示例就是在核对这类"跨框架示例平价"(仓库用 COMPONENT_EXAMPLES_DIFF.md 跟踪差异)。
第 3 步:设计示例集
一套合格的示例集应覆盖六类场景:
- 基本用法(Basic):最简单的可用实现;
- 常见变体(Variants):尺寸、颜色、状态;
- 交互状态(Interactive):hover、focus、disabled、error;
- 高级模式(Advanced):组合、受控与非受控;
- 可访问性(Accessibility):键盘导航、ARIA 属性;
- 边界情况与错误处理(Edge cases)。
第 4 步:跨框架实现
为所有受支持框架创建示例,保持 API 平价(API parity),并遵循 framework_patterns.md 中记录的框架专属模式(详见下文"跨框架实现要点")。
第 5 步:更新 Stories 文件(强制步骤)
这一步是强制的——不更新 stories 文件,示例就不会出现在 Storybook 中。四个框架的更新方式各不相同,规范给出了精确的落点:
- React:
packages/react/src/components/{component}/{component}.stories.tsx,按字母序追加一行导出; - Solid:
packages/solid/src/components/{component}/{component}.stories.tsx,同样按字母序追加导出; - Vue:
packages/vue/src/components/{component}/{component}.stories.ts,需同时添加 import 与 render 条目; - Svelte:
packages/svelte/src/lib/components/{component}/{component}.stories.ts,需同时添加 import 与带Component包装的导出。
以仓库真实的 Checkbox 为例,React 与 Solid 的 stories 写法是单行导出(如 checkbox.stories.tsx 中的 export { Disabled } from './examples/disabled.tsx'),而 Svelte 侧(checkbox.stories.ts)则是先 import DisabledExample from './examples/disabled.svelte',再以 render: () => ({ Component: DisabledExample }) 的对象形式导出;Vue 侧(checkbox.stories.ts)同样采用 render: () => ({ components: { Component: XxxExample }, template: '<Component />' }) 的模式。
第 6 步:更新文档(如适用)
组件文档需要包含:与新示例一致的代码片段、具体的实现细节(哪些 props 挂在哪个子组件上、该调用哪些方法)、使用场景与收益、内置能力与组合能力的区分、双向状态同步说明、可访问性备注,以及相关组件的链接。
第 7 步:错误检查
规范特别注明:创建示例时不运行 typecheck 与 lint,保持流程连贯。这与"先产出、后统一验证"的节奏一致,最终的质量把关交给第 9 节的质量保证清单。
跨框架实现要点:四个框架各自该注意什么
在四框架平行实现时,framework_patterns.md 提供了详尽的差异对照,以下是最容易踩坑的几处。
受控状态管理的框架差异
以 Checkbox 的 controlled 示例为例,四框架的状态写法完全不同:
- React:
useState,onCheckedChange={(e) => setChecked(e.checked)},见 controlled.tsx; - Solid:
createSignal,通过value()读取、setValue更新; - Vue:
ref+v-model:checked,见 controlled.vue; - Svelte:Svelte 5 runes,
let checked = $state(false)+bind:checked,见 controlled.svelte。
图标导入约定
示例中若使用 Lucide 图标,必须遵守"带 Icon 后缀"的命名约定(XIcon、CheckIcon、ChevronDownIcon),并分别从 lucide-react、lucide-solid、lucide-vue-next、lucide-svelte 导入,不得用 as 重命名。
布尔属性的三态逻辑
Zag.js 的状态机依赖 undefined 来触发默认值。因此:
- React/Solid 中写
disabled?: boolean(缺省即 undefined,正确),不要写disabled?: boolean = false; - Vue 中使用
withDefaults(defineProps<Props>(), { disabled: undefined } satisfies BooleanDefaults<Props>); - Svelte 中写
let { disabled = $bindable(undefined) } = $props()。
emit 与回调的差异
Vue 走 emit 路线('update:checked' 用于 v-model、checkedChange 用于回调),其余框架统一用 onCheckedChange?: (details: ChangeDetails) => void 回调 props。
Solid 的 SSR 安全要求
在 Solid 组件中,必须使用 ark.<element> 工厂而非原生 HTML 元素(如 <ark.div> 而不是 <div>)。原因是原生元素会编译为 solid-js/web 的 template() / use() 调用,这些函数只存在于浏览器构建,在 Deno 等 SSR 环境会崩溃;ark 工厂内部使用 Dynamic,将元素创建推迟到渲染时,因此是 SSR 安全的。条件渲染也应使用 <Show> 组件而非布尔表达式。
asChild 模式:四个框架的四种形态
asChild 允许用自定义元素替换组件根元素而保留其功能与 props,四框架实现差异显著:
| 框架 | Prop 名 | 类型 | 用法 |
|---|---|---|---|
| React | asChild |
boolean |
布尔标志 + 包裹子元素 |
| Solid | asChild |
(props: PropsFn) => JSX.Element |
渲染函数,需调用 itemProps() 展开 props |
| Vue | as-child |
boolean |
模板中 kebab-case |
| Svelte | asChild |
Snippet<[PropsFn]> |
{#snippet asChild(itemProps)} 块 |
典型应用场景:把菜单项渲染成链接、自定义触发器、与路由库(Next.js Link、Vue Router、SvelteKit Link)组合。
示例结构模板:从 Basic 到 AdvancedComposition
规范给出每个示例应遵循的结构骨架:
// 清晰、描述性的命名
export const BasicUsage = () => (
// 最小化、聚焦的实现
// 每个示例只清晰演示一个概念
)
export const WithVariants = () => (
// 并排展示不同变体,便于用户对比
)
export const InteractiveStates = () => (
// 演示状态变化与组件行为
)
export const AdvancedComposition = () => (
// 组合多特性的真实复杂场景
)
仓库中的实际命名遵循 kebab-case 语义:basic、controlled、disabled、default-checked、indeterminate、root-provider、with-field、with-form 等,与文档中 <Example id="..." /> 的 id 一一对应。
代码质量标准
- TypeScript First:所有示例必须完整类型标注,禁止
any; - 无注释:遵循仓库"代码自文档化"的极简注释风格;
- 绝对导入:一律从框架包导入(如
@ark-ui/react/checkbox、@ark-ui/solid/checkbox); - 可访问性:每个示例都须符合 ARIA 规范;
- 一致性:四框架 API 保持一致;
- 真实世界模式:示例要贴近真实用法,而非刻意构造的场景。
Root Provider 模式:示例中最关键的高级用法
root-provider 示例几乎是每个组件的标配,它演示的是外部状态管理:调用 useComponent(props) 拿到组件 API/store,再把它传给 Component.RootProvider,从而在任何位置直接访问状态与方法(如 .setChecked()、.checked)。
规范强调两种使用方式二选一、绝不混用:
- 方式一(声明式):props 直接传给
Component.Root,组件自管内部状态,对状态与方法访问有限; - 方式二(编程式):
useCheckbox()+Checkbox.RootProvider value={checkbox},获得完整的状态与方法访问能力,适合编程式控制与复杂交互。
仓库中 React 的 root-provider 示例 展示了完整形态:先 const checkbox = useCheckbox(),再以 checkbox.checked 控制按钮文案、checkbox.setChecked(true/false) 实现外部勾选/取消;Solid 侧(root-provider.tsx)由于响应式特性改为 checkbox() 函数调用;Vue 侧(root-provider.vue)用 :value="checkbox" 绑定并用 v-if / v-else 切换按钮。四框架演示了完全相同的交互,验证了 API 平价原则。
文档同步:让示例进入官网与 MDX
示例的最终归宿是文档网站。根据 documentation-patterns.md 与 component_patterns.md,组件文档位于 website/src/content/pages/components/,通过 <Example> 组件引用示例:
### Basic Usage
<Example id="basic" />
<!-- 跨组件引用 -->
<Example component="checkbox" id="with-field" />
<!-- 指定框架 -->
<Example id="controlled" framework="react" />
文档正文还需写清:具体实现细节(props 挂载位置、应调用的方法)、使用场景与收益、内置 vs 组合的边界、双向状态同步说明、可访问性备注,以及相关组件链接。一个规范性页面通常按"前置元数据 → <ComponentPreview> → Anatomy → Examples(由简到繁)→ Guides → API Reference(<ComponentTypes> / <ContextType>)→ Accessibility"组织。
质量保证:六项交付前检查
示例交付前必须通过以下 QA 清单:
- 所有示例无 TypeScript 编译错误;
- 示例在四个框架中均能正常工作;
- 用键盘导航实测可访问性(Tab、方向键、Enter/Space 激活);
- 确认 stories 在 Storybook 中正确渲染;
- 文档示例与 story 实现一致;
- 示例遵循既有代码库模式。
配合仓库测试实践(见 component_patterns.md 的 Testing Guidelines),可用 vitest-axe 做无障碍断言(expect(results).toHaveNoViolations()),用 fireEvent.keyDown 验证键盘导航与焦点管理,用 userEvent 验证受控/非受控行为。运行 bun run test 或 bun run typecheck(各包均支持 dev / build / test / typecheck / lint 命令,见 development.md)做整体验证。
何时寻求澄清与最终输出格式
遇到以下情况应主动向用户确认,而非自行猜测:组件 API 含糊或文档缺失、某个特性的预期用途不明、现有示例中存在冲突模式、可访问性要求不清、框架特定实现细节缺失。
最终交付物按固定结构输出:
- Summary:所创建示例的概览;
- Files Modified:变更文件清单及说明;
- Example Code:每个框架完整可运行的示例代码;
- Story Updates:
*.stories.*文件的改动; - Documentation Updates:docs/markdown 文件的改动;
- Testing Notes:如何验证示例正确工作。
一句话收尾:这套流程的最终目标是让每个组件示例做到"生产级、组织良好、可读可引用",成为使用该组件最权威的参考——这正是 Ark UI 四框架一致体验得以长期维持的工程保障。