Storybook Controls 实战:用 Args 定义 Button 组件 variant 参数并配置 Radio 单选控件
本指南围绕 Storybook 中一个典型场景展开:组件 Button 的 variant 参数只接受 primary 或 secondary 两个枚举值,如何在故事(story)中用 args 为默认故事(Primary)声明该变体,又如何通过 argTypes 把 Controls 面板默认渲染的「自由文本输入框」升级为更直观的「Radio 单选组」。读完本文,你将掌握 Args 与 ArgTypes 的分工关系、CSF 3 / CSF Next / Svelte CSF 三种写法在 React、Angular、Vue、Svelte、Web Components 框架下的等价实现,以及 Controls 面板的常用进阶配置。
一、场景还原:为什么 variant: 'primary' 不够用
Storybook 的 Controls 面板提供了一种无需编写代码即可动态修改组件参数的图形化交互方式。它的工作前提是:你的故事必须基于 Args 编写。当你给某个故事声明了 args 后,Storybook 会根据组件定义与 args 的初始值自动推断并生成对应的控件。
例如在一个 Button 组件的故事中,我们希望默认故事以主按钮形态(variant: 'primary')展示,其 Button.stories.ts 写法如下(下文各小节会给出全部框架版本):
const meta: Meta<Button> = {
component: Button,
};
export default meta;
export const Primary: Story = {
args: {
variant: 'primary',
},
};
当把 variant 作为 args 暴露出来时,Storybook 依据初始字符串值 'primary' 推断它会渲染一个 string 类型控件,也就是图一中 Controls 面板里的自由文本输入框。虽然输入合法字符串也能工作,但 variant 的实际合法取值只有 primary 与 secondary 两个,自由文本输入显然不是最优交互。
要改善这一点,需要声明自定义 argType:argTypes 会编码参数的名称、描述、默认值等元数据,并允许用户附加 control、options 等注解来精确控制控件类型。
二、用 args 定义故事:让 variant: 'primary' 成为每个框架的默认态
button-story-controls-primary-variant.md 这一代码片段展示了上述场景在 CSF 3、CSF Next 🧪 与 Svelte CSF 三种故事语法、多个渲染器下的等价实现。它们表达的是同一条规则:为单个故事附加 args,把 variant 的默认值设为 'primary'。
2.1 CSF 3 经典写法(React / Vue / Svelte / Angular 等框架)
CSF 3 通过 default export(即 meta)声明组件,用命名导出声明故事,类型注解使用 Meta、StoryObj:
import type { Meta, StoryObj } from '@storybook/your-framework'; // react-vite、nextjs、vue3-vite 等
import { Button } from './Button';
const meta = {
component: Button,
} satisfies Meta<typeof Button>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Primary: Story = {
args: {
variant: 'primary',
},
};
- React / Vue / Preact 等框架:
component指向你导出的组件引用;@storybook/your-framework处按实际包名替换(如@storybook/react-vite)。 - Svelte(TS):写法一致,仅把
@storybook/your-framework换成svelte-vite或sveltekit,并把 import 改为import Button from './Button.svelte'。 - Angular(TS):类型来自
@storybook/angular,组件为import { Button } from './button.component',meta 写作const meta: Meta<Button> = { component: Button };。 - Web Components(JS):由于无需「引入组件实例」,
component传的是自定义元素标签名'demo-button',同时args直接对该标签的属性生效:
export default {
component: 'demo-button',
};
export const Primary = {
args: {
variant: 'primary',
},
};
2.2 CSF Next 🧪 写法(preview.meta 单对象组合)
CSF Next(带 🧪 实验标识的新一代故事语法)把 meta 与 story 收敛到一个 preview 对象上,通过 preview.meta({ ... }) 声明组件注解、通过 meta.story({ ... }) 声明单条故事,从而获得更好的类型收窄与复用能力:
import preview from '../.storybook/preview';
import { Button } from './Button';
const meta = preview.meta({
component: Button,
});
export const Primary = meta.story({
args: {
variant: 'primary',
},
});
该结构在 Angular、Vue(import Button from './Button.vue')、Web Components(component: 'demo-button')下完全等价,JS 与 TS 版本除是否书写类型外无差异;仓库内同时保留 JS 片段是为了在过渡期内同时提供两种语法的可复制代码。
2.3 Svelte CSF 写法(defineMeta + <Story> 模板)
Svelte CSF 是面向 Svelte 的声明式故事格式,在 <script module> 中通过 @storybook/addon-svelte-csf 的 defineMeta 定义组件与注解,并在模板中用 <Story> 标签声明故事:
<script module>
import { defineMeta } from '@storybook/addon-svelte-csf';
import Button from './Button.svelte';
const { Story } = defineMeta({
component: Button,
});
</script>
<Story name="Primary" args={{ variant: 'primary' }} />
2.4 与组件级 args、组合式 args 的衔接
如果大部分故事都需要 variant,更推荐把默认值提升到 组件级 args(meta.args),只有个别故事再覆盖,参见 button-story-component-args-primary.md;通过 ES 2015 展开语法可复用其他故事的 args(button-story-primary-long-name.md),需要共享多参数时可用 args 组合(button-story-primary-composition.md)。args 本身是 JSON 可序列化对象,可在 story、component 与全局(.storybook/preview.* 的 args)三个层级定义,低层级覆盖高层级。
三、用 argTypes 把文本输入升级为 Radio 单选组
现在把 variant 的控件从文本输入换成 radio。button-story-controls-radio-group.md 片段给出的做法是:在 meta(CSF 3 的 default export,或 CSF Next 的 preview.meta,或 Svelte CSF 的 defineMeta)中为 variant 声明自定义 argType,同时给出 options: ['primary', 'secondary'] 与 control: { type: 'radio' }:
import type { Meta } from '@storybook/your-framework';
import { Button } from './Button';
const meta = {
component: Button,
argTypes: {
variant: {
options: ['primary', 'secondary'],
control: { type: 'radio' },
},
},
} satisfies Meta<typeof Button>;
export default meta;
对应 Svelte CSF 的等价写法:
<script module>
import { defineMeta } from '@storybook/addon-svelte-csf';
import Button from './Button.svelte';
const { Story } = defineMeta({
component: Button,
argTypes: {
variant: {
options: ['primary', 'secondary'],
control: { type: 'radio' },
},
},
});
</script>
<Story name="Primary" args={{ variant: 'primary' }} />
Web Components 同样把 argTypes 挂到 meta(CSF 3 时可直接写在 export default 对象上),Angular 版只需将类型改为 Meta<Button> 并沿用 button-story-controls-radio-group.md 中的完整框架版本。配置生效后,Controls 面板中的 variant 即从文本输入框变为一目了然的两个单选选项(图二),同时 options 也约束了可选项集合,避免输入非法值。
四、核心机制:控件推断与 argTypes 覆盖
理解上述改动的底层逻辑需要分清两层机制:
-
自动推断(默认行为):当故事声明了 args 或 meta 声明了
component时,Storybook 依据各框架的文档生成工具自动产出 argTypes。不同框架的数据来源不同:- React:基于初始值 + 组件元数据,通过
react-docgen提取(含 TypeScript 一等支持); - Vue:通过
vue-docgen-api提取props、events、slots; - Angular 的
@storybook/angular-vite:服务端直接读取 TS 源码推断inputs、outputs、properties、methods,无需额外工具链;而@storybook/angular依赖 Compodoc 输出的元数据; - Web Components:通过
custom-elements.json(由@custom-elements-manifest/analyzer生成)在.storybook/preview.*中启用; - Ember:依赖
@storybook/ember-cli-storybook适配器生成的storybook-docgen/index.json。
上述推断逻辑的完整说明见 docs/essentials/controls.mdx。数值类型的参数默认得到
number控件,布尔值默认得到开关(toggle),而像variant这样以字符串初值推断出的枚举类型,若不额外配置,默认就是文本输入——这正是需要手工声明 argType 的原因。 - React:基于初始值 + 组件元数据,通过
-
argTypes 显式覆盖:
control注解(如{ type: 'radio' })连同options、mapping、labels、if等字段编码了该参数在 UI 中的呈现与解析方式,优先级高于自动推断结果,且允许在 meta 或单条 story 上做细粒度覆盖。
4.1 枚举类参数的全部控件形态
options + control 组合不仅支持 radio,还可选择下表罗列的任意枚举控件形态(完整示例见 gizmo-story-controls-customization.md):
| 控件 | 适用场景 | 示例 |
|---|---|---|
radio |
单选,按钮式纵排 | control: { type: 'radio', options: [...] } |
inline-radio |
单选,按钮式横排 | control: 'inline-radio' + options |
select |
单选下拉 | control: 'select' + options |
multi-select |
多选下拉 | control: 'multi-select' + options |
check |
复选(竖排) | control: 'check' + options |
inline-check |
复选(横排) | control: 'inline-check' + options |
布尔、数值、对象、数组、字符串等数据类型对应 boolean、number/range、object、file、text/color/date 等控件,完整矩阵(含 min、max、step、accept、presetColors 等子配置)均收录于 docs/essentials/controls.mdx。其中 date 控件在值变化时会把日期转成 UNIX 时间戳,需要真实日期时须在故事实现里自行转换,这是当前实现的一个已知限制。
4.2 通过控件名匹配颜色与日期
Controls 还支持基于参数名正则自动匹配 color(默认 /(background|color)$/i)与 date(默认 /Date$/)两种控件,在 .storybook/preview.ts|tsx 中通过 controls.matchers 自定义,参考 storybook-addon-controls-custom-matchers.md。
五、Args 的其它增强用法:mapping、URL 传参与条件显示
掌握 radio 之后,以下用法可与 Controls 深度配合:
- 复杂值映射:
variant这类枚举通常直接消费字符串即可;若 args 最终需要 JSX、对象等无法被 URL/序列化表达的值,可用mapping把原始值映射为渲染前的复杂值,并用control.labels自定义 radio/checkbox/select 的展示文案。mapping与control.labels都允许只列部分选项,未列出的选项将原样使用(参考 component-story-custom-args-mapping.md 与 component-story-custom-args-complex.md)。 - URL 直接传参:Control 面板的每次修改都会被编码进 URL 的
args查询参数(?path=/story/button--primary&args=variant:primary),因而每个参数状态都可深度链接分享。出于 XSS 防护,URL 中的键与值仅限字母数字、空格、下划线与短横线。 - 条件显示控件:当「某控件是否显示」依赖另一个参数值时,可在 argType 中使用
if查询对象——须包含arg(参数 ID)或global(全局 ID)目标,并可选truthy、exists、eq、neq运算符之一(缺省等价于{ truthy: true })。例如「高级设置」仅在高级开关开启时显示(component-story-conditional-controls-toggle.md),或两个互斥参数二选一(component-story-conditional-controls-mutual-exclusion.md)。 - 按需展示与排序:
parameters.controls支持include/exclude(字符串数组或正则)过滤控件,支持sort取none/alpha/requiredFirst排序;针对单条属性可用argTypes: { variant: { control: false } }禁用控件而保留文档(详见 component-story-disable-controls.md、component-story-sort-controls.md 与 controls.mdx)。 - 直接在面板中改故事:Controls 面板支持把调整后的参数另存为新故事,或就地回写故事源码,可通过
parameters.controls.disableSaveFromUI: true关闭该能力。
仓库的端到端测试 addon-controls.spec.ts 即针对上述面板交互行为进行验证,可作为阅读实际运行语义的参考入口。
六、小结
对一个枚举型组件参数做「正确」的 Controls 支持需要两步:
- 用 Args 描述组件的默认渲染状态(
args: { variant: 'primary' }),让故事可以被 Controls 动态编辑; - 用 ArgTypes 描述可选项与控件形态(
options+control),让编辑体验从自由文本输入升级为 radio、select 等结构化控件。
两步分别对应于 button-story-controls-primary-variant.md 与 button-story-controls-radio-group.md 这两个代码片段,且无论使用 CSF 3、CSF Next 还是 Svelte CSF,规则在 React、Angular、Vue、Svelte、Web Components 下都保持一致。控件类型、面板参数(disable、exclude、expanded、include、presetColors、sort、disableSaveFromUI)与条件控件的完整 API 说明,可进一步查阅 docs/essentials/controls.mdx 与 docs/api/arg-types.mdx。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0627
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00

