Widgetbook has four building blocks.
| Concept | Definition | Declared in |
|---|---|---|
| Story | One state of a widget | A $-prefixed variable in a .stories.dart file |
| Arg | One widget property, exposed as a UI control | A story's args |
| Mode | One fixed value for an Addon | A story's or scenario's modes |
| Scenario | A story frozen for automated testing | A story's scenarios |
A Story is one state of a widget.
A button has $Primary, $Secondary, $Disabled.
Each appears as its own entry in the navigation tree.
Meta selects the widget constructor.
Each $ variable is a story.
const meta = Meta(Button.new);
final $Primary = _Story(/* ... */);
final $Secondary = _Story(/* ... */);_Story and _Args are generated from the constructor passed to Meta.
See Code Generation.
An Arg is one widget property, exposed as a control in the Widgetbook UI.
Args are derived from the constructor parameters of the widget in Meta, so they are type-safe.
final $Primary = _Story(
args: _Args(
label: StringArg('Primary'), // editable control
onPressed: Arg.fixed(() {}), // pinned, no control
),
);StringArg,IntArg,EnumArg, and otherArgtypes render a control.Arg.fixedpins the value, for callbacks and values that are not worth tweaking.
Args change what the widget is. Modes change the environment it renders in: theme, locale, viewport, text scale.
That environment comes from Addons. An addon wraps every story and accepts a range of values. A Mode is one of those values, pinned.
final config = Config(
// ...
addons: [
MaterialThemeAddon({
'Light': ThemeData.light(),
'Dark': ThemeData.dark(),
}),
ViewportAddon(Viewports.all),
],
);final $Primary = _Story(
// ...
modes: [
MaterialThemeMode('Dark', ThemeData.dark()),
ViewportMode(IosViewports.iPhone13),
],
);Story modes apply to all of the story's scenarios. See Modes.
A Scenario is a story frozen into a fixed state so it can be tested. Args in a scenario are hard-coded. Same inputs, same output, every run.
final $Primary = _Story(
args: _Args(
label: StringArg('Primary'),
onPressed: Arg.fixed(() {}),
),
scenarios: [
_Scenario(
name: 'Long Label',
args: _Args.fixed(
label: 'This is a very long label',
onPressed: () {},
),
modes: [MaterialThemeMode('Light', ThemeData.light())],
),
],
);Scenarios run on flutter_test.
The optional run callback receives a WidgetTester for taps, pumps, and assertions.
flutter test captures a screenshot of every scenario into build/.widgetbook.
One story uses all four:
const meta = Meta(Button.new);
final $Primary = _Story(
// Widget properties, adjustable while browsing.
args: _Args(
label: StringArg('Primary'),
onPressed: Arg.fixed(() {}),
),
// Environment inherited by every scenario below.
modes: [
ViewportMode(IosViewports.iPhone13),
],
// Fixed states captured during `flutter test`.
scenarios: [
_Scenario(name: 'Default'),
_Scenario(
name: 'Dark',
modes: [MaterialThemeMode('Dark', ThemeData.dark())],
),
],
);In the UI: one entry, Primary, with a live label control.
Story modes are not applied while browsing, so the viewport stays on whatever the addon is set to.
From flutter test: two snapshots, Default on iPhone 13 and Dark on iPhone 13 with a dark theme.
Scenario modes override story modes of the same type.
The Dark scenario keeps the story's viewport and replaces the theme.
See How Modes Are Merged.
- Install & Quick Start for setting up your Widgetbook package.
- Code Generation for what the generator produces and when to run it.
- Stories for the full Story API.
- Testing for writing Scenarios and capturing snapshots.

