首页
/ Storybook Controls 实战:用 Args 定义 Button 组件 variant 参数并配置 Radio 单选控件

Storybook Controls 实战:用 Args 定义 Button 组件 variant 参数并配置 Radio 单选控件

2026-09-07 13:59:08作者:申梦珏Efrain

本指南围绕 Storybook 中一个典型场景展开:组件 Buttonvariant 参数只接受 primarysecondary 两个枚举值,如何在故事(story)中用 args 为默认故事(Primary)声明该变体,又如何通过 argTypes 把 Controls 面板默认渲染的「自由文本输入框」升级为更直观的「Radio 单选组」。读完本文,你将掌握 Args 与 ArgTypes 的分工关系、CSF 3 / CSF Next / Svelte CSF 三种写法在 React、Angular、Vue、Svelte、Web Components 框架下的等价实现,以及 Controls 面板的常用进阶配置。

Storybook Controls 默认将 variant 渲染为自由文本输入框,面板位于预览画布下方

一、场景还原:为什么 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 的实际合法取值只有 primarysecondary 两个,自由文本输入显然不是最优交互。

要改善这一点,需要声明自定义 argType:argTypes 会编码参数的名称、描述、默认值等元数据,并允许用户附加 controloptions 等注解来精确控制控件类型。

二、用 args 定义故事:让 variant: 'primary' 成为每个框架的默认态

button-story-controls-primary-variant.md 这一代码片段展示了上述场景在 CSF 3CSF Next 🧪Svelte CSF 三种故事语法、多个渲染器下的等价实现。它们表达的是同一条规则:为单个故事附加 args,把 variant 的默认值设为 'primary'

2.1 CSF 3 经典写法(React / Vue / Svelte / Angular 等框架)

CSF 3 通过 default export(即 meta)声明组件,用命名导出声明故事,类型注解使用 MetaStoryObj

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-vitesveltekit,并把 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-csfdefineMeta 定义组件与注解,并在模板中用 <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,更推荐把默认值提升到 组件级 argsmeta.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 后,variant 以 Radio 单选组呈现,交互更直观

四、核心机制:控件推断与 argTypes 覆盖

理解上述改动的底层逻辑需要分清两层机制:

  1. 自动推断(默认行为):当故事声明了 args 或 meta 声明了 component 时,Storybook 依据各框架的文档生成工具自动产出 argTypes。不同框架的数据来源不同:

    • React:基于初始值 + 组件元数据,通过 react-docgen 提取(含 TypeScript 一等支持);
    • Vue:通过 vue-docgen-api 提取 propseventsslots
    • Angular 的 @storybook/angular-vite:服务端直接读取 TS 源码推断 inputsoutputspropertiesmethods,无需额外工具链;而 @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 的原因。

  2. argTypes 显式覆盖control 注解(如 { type: 'radio' })连同 optionsmappinglabelsif 等字段编码了该参数在 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

布尔、数值、对象、数组、字符串等数据类型对应 booleannumber/rangeobjectfiletext/color/date 等控件,完整矩阵(含 minmaxstepacceptpresetColors 等子配置)均收录于 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 的展示文案。mappingcontrol.labels 都允许只列部分选项,未列出的选项将原样使用(参考 component-story-custom-args-mapping.mdcomponent-story-custom-args-complex.md)。
  • URL 直接传参:Control 面板的每次修改都会被编码进 URL 的 args 查询参数(?path=/story/button--primary&args=variant:primary),因而每个参数状态都可深度链接分享。出于 XSS 防护,URL 中的键与值仅限字母数字、空格、下划线与短横线。
  • 条件显示控件:当「某控件是否显示」依赖另一个参数值时,可在 argType 中使用 if 查询对象——须包含 arg(参数 ID)或 global(全局 ID)目标,并可选 truthyexistseqneq 运算符之一(缺省等价于 { truthy: true })。例如「高级设置」仅在高级开关开启时显示(component-story-conditional-controls-toggle.md),或两个互斥参数二选一(component-story-conditional-controls-mutual-exclusion.md)。
  • 按需展示与排序parameters.controls 支持 include / exclude(字符串数组或正则)过滤控件,支持 sortnone / alpha / requiredFirst 排序;针对单条属性可用 argTypes: { variant: { control: false } } 禁用控件而保留文档(详见 component-story-disable-controls.mdcomponent-story-sort-controls.mdcontrols.mdx)。
  • 直接在面板中改故事:Controls 面板支持把调整后的参数另存为新故事,或就地回写故事源码,可通过 parameters.controls.disableSaveFromUI: true 关闭该能力。

仓库的端到端测试 addon-controls.spec.ts 即针对上述面板交互行为进行验证,可作为阅读实际运行语义的参考入口。

六、小结

对一个枚举型组件参数做「正确」的 Controls 支持需要两步:

  1. Args 描述组件的默认渲染状态(args: { variant: 'primary' }),让故事可以被 Controls 动态编辑;
  2. ArgTypes 描述可选项与控件形态(options + control),让编辑体验从自由文本输入升级为 radio、select 等结构化控件。

两步分别对应于 button-story-controls-primary-variant.mdbutton-story-controls-radio-group.md 这两个代码片段,且无论使用 CSF 3、CSF Next 还是 Svelte CSF,规则在 React、Angular、Vue、Svelte、Web Components 下都保持一致。控件类型、面板参数(disableexcludeexpandedincludepresetColorssortdisableSaveFromUI)与条件控件的完整 API 说明,可进一步查阅 docs/essentials/controls.mdxdocs/api/arg-types.mdx

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.13 K
2.75 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
857
1.35 K
docsdocs
暂无描述
Markdown
897
5.8 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
529
593
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
915
1.83 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.58 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.35 K
1.46 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.01 K
515
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
547
388