Widgetbook uses Dart's build_runner to generate the boilerplate needed for Stories, Args, and Scenarios.
At the core of this system is Meta, a declaration that selects a widget constructor to generate stories for.
From a Meta declaration and a part directive, the generator produces type-safe args, story classes, and a component registry.
This lets you focus on writing stories rather than wiring up boilerplate.
To set up code generation for a widget, create a .stories.dart file with two things: a part directive pointing to the generated file, and a Meta variable with a constructor tear-off:
import 'package:widgetbook/widgetbook.dart';
import 'package:my_app/counter.dart';
part 'counter.stories.g.dart';
const meta = Meta(Counter.new); The part directive is required since the generator writes all output into the corresponding .stories.g.dart file. Meta selects the constructor to inspect; the component widget is derived from the tear-off.
Args are derived from that constructor's parameters, and the generator creates a type-safe _Args class, a _Story class with a default builder, and other generated declarations.
Meta takes a single positional argument: a constructor tear-off such as Counter.new (the unnamed constructor) or Counter.compact (a named constructor). Meta variables must be declared as const.
The name of the meta variable itself doesn't matter, the generator finds it by type.
To customize the component as a whole, declare an optional ComponentMeta variable (at most one per stories file).
All its parameters are optional:
| Parameter | Type | Description |
|---|---|---|
name | String | Override the display name shown in the Widgetbook UI. Defaults to the widget class name. |
path | String | Set the navigation path in the Widgetbook UI. By default, the path is derived from the file's directory structure. Wrap folder names in square brackets to create categories (e.g. [components]/counter). |
docsBuilder | DocsBuilderFunction | Customize the documentation blocks shown for this component. |
// Must be `final` instead of `const` since `docsBuilder` holds a closure.
final component = ComponentMeta(
name: 'Step Counter',
path: '[components]/counter',
docsBuilder: (blocks) => blocks.replaceFirst<DartCommentDocBlock>(
const TextDocBlock('A counter with increment and decrement controls.'),
),
);
const meta = Meta(Counter.new);A ComponentMeta without parameters is redundant, so omit it unless you need one of the customizations above.
Once the generator has run, you can define stories using the generated types.
Each story variable must start with $.
The variable name (without the $) becomes the story's display name in the Widgetbook UI:
final $Default = _Story(
args: _Args(
initialValue: IntArg(0),
),
);
final $StartAtTen = _Story(
args: _Args(
initialValue: IntArg(10),
),
);Widgets often have multiple constructors that represent distinct variants, like MyButton() and MyButton.icon().
A stories file can have any number of Meta variables, one per constructor, all targeting the same widget:
const meta = Meta(MyButton.new);
const iconMeta = Meta(MyButton.icon); For each Meta, the generator emits a full set of types.
Types for named constructors are prefixed with the PascalCase constructor name:
// Stories for MyButton(...)
final $Default = _Story();
// Stories for MyButton.icon(...)
final $Icon = _IconStory();
final $IconLarge = _IconStory(
args: _IconArgs(size: DoubleArg(48)),
);All stories appear in a flat list under the same component, with the story variable's name as the display name. Factory constructors work the same way as named constructors.
For cases where you need a different args shape, provide Meta.argsType with a constructor tear-off of a plain Dart class.
The generated args are then derived from that constructor's parameters instead of the widget's:
Since the generator can no longer infer how to construct the widget from these custom args, you must provide a builder, either per story or via a shared defaults variable.
import 'package:widgetbook/widgetbook.dart';
import 'package:my_app/label_badge.dart';
part 'label_badge.stories.g.dart';
const meta = Meta(LabelBadge.new, argsType: NumericBadgeInput.new);
class NumericBadgeInput {
NumericBadgeInput({required this.number});
final int number;
} Each variant must have a distinct args type.
With a custom args type, each story needs a builder that maps the custom args to the actual widget:
final $Primary = _Story(
args: _Args(
number: IntArg(1),
),
builder: (context, args) {
return LabelBadge(
text: args.number.toString(),
);
},
);To avoid repeating the same builder and setup in every story, define a variable of the generated _Defaults type.
The generated _Story class picks it up automatically:
final defaults = _Defaults(
setup: (context, child, args) {
return Container(
padding: const EdgeInsets.all(8),
color: Colors.grey[300],
child: child,
);
},
builder: (context, args) {
return LabelBadge(
text: args.number.toString(),
);
},
);The generator matches a defaults variable to its variant by the variable's type, not its name. _Defaults applies to the unnamed constructor's stories, _IconDefaults to the .icon constructor's stories, and so on.
Each variant can have at most one defaults variable.
With defaults in place, stories only need to specify their args:
final $Primary = _Story(
args: _Args(
number: IntArg(1),
),
);
final $Secondary = _Story(
args: _Args(
number: IntArg(2),
),
);Individual stories can still override builder or setup when needed.
- The widget constructor has parameters that don't work well as args (callbacks, controllers, complex objects).
- You want to expose a simplified or curated set of interactive controls.
- Multiple widget parameters should be derived from a single arg value.
- The widget depends on a provider and you want args to mirror the provider's properties instead of the widget's constructor.
This lets you mock the provider in
setupusing the arg values. See the Mocking guide for more details.
Running the generator produces two kinds of output:
- A per-story part file (
.stories.g.dart) for each story file. - A global component registry (
components.g.dart) at the library root.
For each .stories.dart file, a .stories.g.dart part file is generated containing one set of declarations per Meta variable.
For named constructors, the PascalCase constructor name is inserted after the underscore (e.g. _IconStory) and after the widget name (e.g. MyButtonIconStory):
| Declaration | Full Name | Description |
|---|---|---|
_Args | {Widget}Args (or {ArgsClass}Args for custom args) | Extends StoryArgs<TWidget>. Contains one Arg field per constructor parameter (or per argsType constructor parameter when using custom args), getters for resolved values, and a .fixed() constructor. See Args. |
_Story | {Widget}Story | Extends Story<TWidget, Args>. When using Meta, includes a default builder that constructs the widget from args. Accepts args, setup, modes, scenarios, and builder. See Stories. |
_Scenario | {Widget}Scenario | Typedef for Scenario<TWidget, Args>. Used to define fixed, testable states. See Scenarios. |
_Defaults | {Widget}Defaults | Typedef for Defaults<TWidget, Args>. Used to share a setup and builder across the variant's stories in the file. |
_Component | Component<TWidget, StoryArgs<TWidget>> | Typedef for the component registration type. Generated once per file, as stories from all variants share one component. |
The underscore-prefixed names (_Story, _Args, etc.) are private typedefs that point to the fully qualified generated classes.
They exist so you don't have to repeat the widget name everywhere:
// Without syntactic sugar, using the full generated names:
final $Primary = CounterStory(
args: CounterArgs(
initialValue: IntArg(0),
),
);
// With syntactic sugar, using the underscore aliases:
final $Primary = _Story(
args: _Args(
initialValue: IntArg(0),
),
);Both forms are equivalent. The underscore aliases keep story files concise and consistent regardless of the widget name.
The generated _Args class wraps each constructor parameter in a typed Arg<T>.
It provides three ways to interact with each parameter:
- Arg fields (e.g.
initialValueArg): the fullArg<T>object, useful when you need to customize the arg's behavior. - Value getters (e.g.
initialValue): convenience getters that return the resolved value directly. .fixed()constructor: creates args with constant values that won't be interactive in the Widgetbook UI. Useful for Scenarios.
// Interactive args with UI controls:
final $Default = _Story(
args: _Args(
initialValue: IntArg(0),
),
);
// Fixed args without UI controls (useful for scenarios):
_Scenario(
name: 'Start at 10',
args: _Args.fixed(
initialValue: 10,
),
);A single components.g.dart file is generated at the library root.
It collects every {Widget}Component variable into a list that Widgetbook uses to build the navigation tree:
final components = <Component>[
CounterComponent,
ButtonComponent,
LabelBadgeComponent,
];This file is regenerated whenever you add or remove story files.
Run the generator from your Widgetbook package directory:
dart run build_runner buildFor a smoother workflow, run the generator in watch mode.
It re-generates automatically on every file save, so the generated types (_Story, _Args, etc.) stay up to date and your IDE autocompletion always works:
dart run build_runner watchWatch mode is strongly recommended during development. It removes the need to manually re-run the build command every time you add a story, change args, or modify your widget's constructor.
- Use watch mode during development. Running
dart run build_runner watchkeeps generated code in sync with your source files automatically. - Set up
Metabefore writing stories. Create themetavariable andpartdirective first, run the generator once, and then start writing stories. This ensures_Story,_Args, and other generated types are available for autocompletion in your IDE. - Prefer widget-derived args over custom args. Without
argsType, you get an auto-generatedbuilder, so you don't need to manually map args to widget parameters. Only provideargsTypewhen the widget's constructor doesn't fit your needs. - Prefix story variables with
$. The generator requires this prefix (e.g.$Default,$Primary) and strips the$to derive the display name shown in the Widgetbook UI.

