Core Concepts

Widgetbook has four building blocks.

ConceptDefinitionDeclared in
StoryOne state of a widgetA $-prefixed variable in a .stories.dart file
ArgOne widget property, exposed as a UI controlA story's args
ModeOne fixed value for an AddonA story's or scenario's modes
ScenarioA story frozen for automated testingA story's scenarios

Story

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.

dart
widgetbook/lib/button.stories.dart
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.

Arg

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.

dart
widgetbook/lib/button.stories.dart
final $Primary = _Story(
  args: _Args(
    label: StringArg('Primary'),   // editable control
    onPressed: Arg.fixed(() {}),   // pinned, no control
  ),
);
  • StringArg, IntArg, EnumArg, and other Arg types render a control.
  • Arg.fixed pins the value, for callbacks and values that are not worth tweaking.

Mode

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.

dart
widgetbook/lib/widgetbook.config.dart
final config = Config(
  // ...
  addons: [
    MaterialThemeAddon({
      'Light': ThemeData.light(),
      'Dark': ThemeData.dark(),
    }),
    ViewportAddon(Viewports.all),
  ],
);
dart
widgetbook/lib/button.stories.dart
final $Primary = _Story(
  // ...
  modes: [
    MaterialThemeMode('Dark', ThemeData.dark()),
    ViewportMode(IosViewports.iPhone13),
  ],
);

Story modes apply to all of the story's scenarios. See Modes.

Scenario

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.

dart
widgetbook/lib/button.stories.dart
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.

Composition

One story uses all four:

dart
widgetbook/lib/button.stories.dart
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.

Next