# Widgetbook Docs

Build your Design System's widgets in isolation.

## Docs

### Overview

Source: https://docs.widgetbook.io/

```mdx
# Overview

Widgetbook is a sandbox for building **widgets and screens in isolation**. 
It helps you develop and share hard-to-reach states and edge cases without needing to run your whole app. 
Widgetbook is [open-source](https://github.com/widgetbook/widgetbook) and **free** to use.

<YouTube id="sGRetvJ-zZI" />

## The Problem

While developing widgets or screens, you have probably faced one of the following problems:

- **Hard-to-reach states:** You need to test a widget in a specific state (e.g. error, loading, etc.) but it is hard to reach that state in your app.
- **Time consuming:** You need to run your whole app to test a single widget or screen, and even spend more time to test in different configurations (e.g. dark mode, different locales, etc.).
- **API/Datasource dependencies:** You need to mock data or APIs to test your screens, which is time-consuming and error-prone, and it is even not always possible to do that.
- **Collaboration:** You need to share your widgets with your design team or other developers, but it is hard to do that without a proper documentation or cataloging system.

## The Solution: Widgetbook

With Widgetbook you can **build UI in isolation** without depending on any external data source or API. This allows you to:

- **Easily test your widgets** in different **states** (e.g. error, loading, etc.) via [Knobs](/knobs/overview), and **configurations** (e.g. dark mode, different locales, etc.) via [Addons](/addons/overview).
- **Mock your data** so you can test your widgets without the need to run your whole app (learn more about [Mocking](/use-cases/mocking)).
- **Catalog your widgets and screens** in a single place, so you can easily share them with all your team members (e.g. developers, designers, product managers, QA testers).

<YouTube id="5cNUX7eDZWE" />

## Customer Showcases

### Salto Systems

Salto Systems is a company that specializes in smart locks and access control systems. They use Widgetbook to build their widgets in isolation, which allows them to speed up their development process.

<Card
  icon="medium fa-brands"
  title="How Salto prevents UI problems while saving 50% of their time"
  href="https://medium.com/widgetbook/how-salto-prevents-ui-problems-while-saving-50-of-their-time-with-widgetbook-53194f79ee96"
>
  > We build new features by starting with Widgetbook, allowing us to
  immediately develop the UI in isolation and see how new components behave.
  Building in isolation is especially helpful for larger components and screens
  that handle various data states — like loading, loaded, or error. Before
  Widgetbook, testing error states was time-consuming because they’re naturally
  hard to reproduce; the data often comes from sources we can’t easily control.
  But with Widgetbook’s isolation-first approach, we can inject mocked data and
  test all component states. Once a component is ready, other developers can
  easily explore its different variations. — Arthur Schenk, Mobile Team Lead at
  Salto
</Card>

### Lotum

Lotum speeds up their app development by building their widgets in isolation with Widgetbook.

<YouTube id="0xV-OzfY2zg" />

### 1KOMMA5°

1KOMMA5° creates an in-house design system which is used across 6 different projects.

<YouTube id="4xWseFS5Z38" />

### LeanCode

LeanCode is a software agency which creates a design system template that they share with every new client for the project start.

<YouTube id="rmQktvnyfPg" />
```

### Overview

Source: https://docs.widgetbook.io/cli

```mdx
# Overview

The CLI is available as the [widgetbook_cli](https://pub.dev/packages/widgetbook_cli) package.

```bash
# 🎯 Activate from https://pub.dev
dart pub global activate widgetbook_cli

# 🚀 Use CLI
widgetbook <command> [arguments]
```

## Commands

The CLI provides the following commands:

- [cloud build push](/cli/commands/cloud-build-push)
- [coverage](/cli/commands/coverage)
```

### Overview

Source: https://docs.widgetbook.io/cloud

```mdx
# Overview

Widgetbook Cloud is a managed hosting solution for Widgetbook that allows you to run golden tests on every commit so that you can catch UI regressions before they reach production.

<Info>
  Get [free demo](https://meetings-eu1.hubspot.com/lucas-josefiak) or [direct
  access](https://app.widgetbook.io/) to Widgetbook Cloud.
</Info>

<YouTube id="l3tj9VvkjLs" />

## The Problem

After submitting a PR for review, both you and the reviewer have no idea which **UI changes** were made. The reviewer has to do the following to make sure that everything works as excepted:

1. Check out the PR locally.
2. Run the app in the simulator or on desktop.
3. Execute the required flow (e.g. authentication) to reach the desired screen to review.
4. Test out the screen in different states (e.g. empty, error, loading, etc.) or different configurations (e.g. dark mode, different locales, etc.).

That process is time-consuming that most reviewers skip it, and just settle down for only reviewing the code. By skipping the UI review process, the risk of introducing UI regressions increases significantly.

<Image
  src="/assets/cloud/code-review.png"
  zoom
  caption="Code review doesn't show how the changes look like"
/>

## The Solution: Widgetbook Cloud

Widgetbook Cloud can help you doing **UI Reviews in your browser** without the need to check out the PR locally or the need to write any code to test the UI. You can easily see all the changes made in the PR and test them in different states or configurations.

<CardGroup>
  <Card title="Builds" icon="hammer" href="/cloud/builds/overview">
    Host a Widgetbook build for each commit without all the hassle of self-hosting. Simple and optimized for Widgetbook.
  </Card>

  <Card title="Reviews" icon="code-compare" href="/cloud/reviews">
    Review UI changes and detect UI regressions before you merge PRs into your main branch.
  </Card>
</CardGroup>

<Image
  src="/assets/cloud/ui-review.png"
  zoom
  caption="UI review gives your review workflow superpowers"
/>

## Customer Showcases

### Salto Systems

Salto Systems is a company that specializes in smart locks and access control systems. They use Widgetbook Cloud to simplify their UI reviews and speed up their development process.

<Card
  icon="medium fa-brands"
  title="How Salto prevents UI problems while saving 50% of their time"
  href="https://medium.com/widgetbook/how-salto-prevents-ui-problems-while-saving-50-of-their-time-with-widgetbook-53194f79ee96"
>
  > Widgetbook is integrated and running in our build CI pipelines. So, every
  merge request triggers Widgetbook Builds to run the snapshot tests. If we made
  changes in code, Widgetbook shows which components stayed the same and which
  components had visual adjustments making it trivial to review visual changes
  and get the code merged. — Arthur Schenk, Mobile Team Lead at Salto
</Card>

### 1KOMMA5°

1KOMMA5° creates an in-house design system which is used across 6 different projects.

<YouTube id="4xWseFS5Z38" />

### LeanCode

LeanCode is a software agency which creates a design system template that they share with every new client for the project start.

<YouTube id="rmQktvnyfPg" />
```

### Quick Start

Source: https://docs.widgetbook.io/quick-start

```mdx
# Quick Start

## Bootstrap

<Info>
  If you are using Widgetbook in a monorepo, check the
  [Monorepo](/essentials/monorepo) guide.
</Info>

The first step to start using Widgetbook is to create a new separate Flutter app for your widget catalog.

1. Create a new Flutter project inside your app's directory:

   ```bash
   ## All platforms
   flutter create widgetbook --empty

   ## Certain platforms
   flutter create widgetbook --empty --platforms=web,macos
   ```

1. To avoid naming conflict with the [Widgetbook pub package](https://pub.dev/packages/widgetbook), change the project `name` to `widgetbook_workspace` inside the `widgetbook/pubspec.yaml` file:

   ```diff
   - name: widgetbook
   + name: widgetbook_workspace
   ```

1. Add the following dependencies to your `widgetbook` project:

   ```bash
   flutter pub add widgetbook widgetbook_annotation dev:widgetbook_generator dev:build_runner
   ```

1. Add your app as a path dependency to the `widgetbook/pubspec.yaml` file:

   ```yaml
   dependencies:
     your_app:
       path: ../
   ```

After finishing the setup, the folder structure should look something like this:

```tree
your_app/
├── pubspec.yaml
├── lib/
├── ...
└── widgetbook/
    ├── pubspec.yaml
    ├── lib/
    └── ...
```

And the `widgetbook/pubspec.yaml` file should look like this:

```yaml
name: widgetbook_workspace
# ...

dependencies:
  widgetbook_annotation: ^{{ versions.annotation }}
  widgetbook: ^{{ versions.widgetbook }}
  your_app:
    path: ../

dev_dependencies:
  build_runner:
  widgetbook_generator: ^{{ versions.generator }}
```

## Your First Use-case

In this section, you will create a simple use-case and show it inside your Widgetbook app.

1. Choose a widget from `your_app` that you want to catalog. **For this example, we will use the imaginary `CoolButton` widget**.

1. Create a file inside your `widgetbook` app at `widgetbook/lib/cool_button.dart`

   <Info>
     If your widget needs some parameters, you can pass some constants for now
     for simplicity. Later you can check how to use [Knobs](/knobs/overview).
   </Info>

   ```dart
   import 'package:flutter/material.dart';
   import 'package:widgetbook_annotation/widgetbook_annotation.dart' as widgetbook;

   // Import the widget from your app
   import 'package:your_app/cool_button.dart';

   @widgetbook.UseCase(name: 'Default', type: CoolButton)
   Widget buildCoolButtonUseCase(BuildContext context) {
     return CoolButton();
   }
   ```

1. Create a file inside your `widgetbook` app at `lib/main.dart`

   ```dart
   import 'package:flutter/material.dart';
   import 'package:widgetbook/widgetbook.dart';
   import 'package:widgetbook_annotation/widgetbook_annotation.dart' as widgetbook;

   // This file does not exist yet,
   // it will be generated in the next step
   import 'main.directories.g.dart';

   void main() {
     runApp(const WidgetbookApp());
   }

   @widgetbook.App()
   class WidgetbookApp extends StatelessWidget {
     const WidgetbookApp({super.key});

     @override
     Widget build(BuildContext context) {
       return Widgetbook.material(
         // The [directories] variable does not exist yet,
         // it will be generated in the next step
         directories: directories,
       );
     }
   }
   ```

1. Run the following command to generate the `main.directories.g.dart` file that has the `directories` variable:

   ```bash
   dart run build_runner build -d
   ```

1. Now you can run your Widgetbook app and see your use-case in action:

   ```bash
   flutter run
   ```

1. Add some [Addons](/addons/overview) to your Widgetbook app to customize the appearance of your use-case.

## Migrating Existing Widgets

If you already have a number of widgets developed in your design system, it may be time consuming to create use-cases for all of them manually.

Our friends at LeanCode developed a VS Code extension - [Widgetbook Entries Generator](https://marketplace.visualstudio.com/items?itemName=LeanCode.widgetbook-generator) - to help with the process, along with an article describing this case and its usage: [How We Boosted Moving Flutter Widgets to Widgetbook](https://leancode.co/blog/moving-flutter-widgets-to-widgetbook?utm_source=widgetbook&utm_medium=docspage).
```

### Resources

Source: https://docs.widgetbook.io/resources

```mdx
# Resources

## Presentation

Often our users need to create a presentation to convince their team to use Widgetbook. That's why we created the following presentation that you can use to explain your team the value and show them how easy it is to setup the open source Widgetbook as well as Widgetbook Cloud. Please find the link to [our Google Slides](https://docs.google.com/presentation/d/1ZIhWEJovIaLJQn-4ZYq8n8miJe8QwQUr63DJ6FjwWPE/edit?usp=sharing) here. To make best use of the slides, feel free to make a copy of them with one of your Google workspace accounts (file > make a copy) and include them in your presentation.

## Talk

<YouTube id="Du5yVtnmNzg" />
```

### Telemetry

Source: https://docs.widgetbook.io/telemetry

```mdx
# Telemetry

Widgetbook Generator collects and reports **anonymous** usage information.
This information is essential to figure out how we should improve our package.
Telemetry does not collect any personal information.

## Opting-out

To opt-out from telemetry:

1. Create a `build.yaml` file at the root of your project (next to your `pubspec.yaml`).
1. Add the following content to the file:

   ```yaml
   targets:
     $default:
       builders:
         widgetbook_generator:telemetry:
           enabled: false
   ```

## Collected Data

A new telemetry report after you run the `build_runner build` command,
if the `*.directories.g.dart` file has changed.

We send the following information, which **has no personal data**:

1. The package name.
1. Unique **hashed** anonymous ID based on your git config email.
1. The SHA of the first commit in the repository, to identify distinct projects.
1. The repository's owner URL (e.g. `https://github.com/widgetbook`).
1. Information about Widgetbook Components like:
   - The number of components.
   - The number of components with various counts of use-cases.
   - The packages' names where the components were defined.

## Further Notice

If you have any questions, please [reach out to us](https://discord.com/invite/zT4AMStAJA).
We’re happy to answer any questions about why we decided to add telemetry or help you to opt-out.
```

### Accessibility Addon

Source: https://docs.widgetbook.io/addons/accessibility-addon

```mdx
# Accessibility Addon

<Warning>
  This addon is **deprecated** due to maintenance issues. Please use the
  [`BuilderAddon` +
  `accessibility_tools`](/addons/builder-addon#accessibility_tools) instead.
</Warning>

Uses the [`accessibility_tools`](https://pub.dev/packages/accessibility_tools) package to detect any accessibility issues in your use-cases.

## Usage

```dart title=widgetbook/lib/main.dart
class WidgetbookApp extends StatelessWidget {
  const WidgetbookApp({super.key});

  @override
  Widget build(BuildContext context) {
    return Widgetbook(
      // ...
      addons: [
        AccessibilityAddon(), // [!code highlight]
      ],
    );
  }
}
```

## Order

Since the [order of addons](/addons/overview#order-of-addons) matters, the `AccessibilityAddon` must be the **last** addon in the list.
```

### Alignment Addon

Source: https://docs.widgetbook.io/addons/alignment-addon

```mdx
# Alignment Addon

Wraps all use-cases with an [`Align`](https://api.flutter.dev/flutter/widgets/Align-class.html) widget. This is useful when you want to center all your use cases without having to wrap each one of them manually.

Without this addon, all your use-cases will be aligned in the top-left corner, unless you wrap them in a `Center` widget or similar.

<Tabs
  values={[
    { label: "Center", value: "center" },
    { label: "Top Left", value: "top-left" },
  ]}
>
  <TabItem value="center">
    <iframe
      src="https://demo.widgetbook.io/#/?path=ui/widgets/primarybutton/default&alignment={alignment:Center}&device={name:None}&preview"
      width="100%"
      height="420px"
    />
  </TabItem>
  <TabItem value="top-left">
    <iframe
      src="https://demo.widgetbook.io/#/?path=ui/widgets/primarybutton/default&alignment={alignment:Top%20Left}&device={name:None}&preview"
      width="100%"
      height="420px"
    />
  </TabItem>
</Tabs>

## Usage

```dart title=widgetbook/lib/main.dart
class WidgetbookApp extends StatelessWidget {
  const WidgetbookApp({super.key});

  @override
  Widget build(BuildContext context) {
    return Widgetbook(
      // ...
      addons: [
        AlignmentAddon(), // [!code highlight]
      ],
    );
  }
}
```

## Order

Since the [order of addons](/addons/overview#order-of-addons) matters, here are some guidelines to follow when using the `AlignmentAddon`:

Addons that should come before the `AlignmentAddon`:

- `ViewportAddon`
- `DeviceFrameAddon`
- `ThemeAddon`
- `MaterialThemeAddon`
- `CupertinoThemeAddon`
- `GridAddon`
- `InspectorAddon`

Addons that should come after the `AlignmentAddon`:

- `ZoomAddon`

## Multi-snapshot Support

The `AlignmentAddon` is supported in the [Multi Snapshot Reviews](/cloud/snapshots/multi-snapshot) via the `AlignmentAddonConfig`. Here's how to configure it:

```dart title=widgetbook/lib/main.dart
import 'package:widgetbook/widgetbook.dart';
import 'package:widgetbook_annotation/widgetbook_annotation.dart';

@App(
  cloudAddonsConfigs: {
    'Center': [
      AlignmentAddonConfig('Center'), // [!code highlight]
    ],
    'Bottom Center': [
      AlignmentAddonConfig('Bottom Center'), // [!code highlight]
    ],
  },
)
class WidgetbookApp extends StatelessWidget {
  const WidgetbookApp({super.key});

  @override
  Widget build(BuildContext context) {
    return Widgetbook(
      // ...
      addons: [
        AlignmentAddon(), // [!code highlight]
      ],
    );
  }
}
```
```

### Builder Addon

Source: https://docs.widgetbook.io/addons/builder-addon

```mdx
# Builder Addon

The `BuilderAddon` is utility that can help you:

1. Wrap all use-cases with a custom widget.
2. Create a [custom addon](/addons/custom-addon) with less boilerplate, but the addon will have no "controls" shown in the addons panel.

## Usage

```dart title=widgetbook/lib/main.dart
class WidgetbookApp extends StatelessWidget {
  const WidgetbookApp({super.key});

  @override
  Widget build(BuildContext context) {
    return Widgetbook(
      directories: directories,
      addons: [
        BuilderAddon( // [!code highlight]
          name: 'Red', // [!code highlight]
          builder: (context, child) => ColoredBox( // [!code highlight]
            color: Colors.red, // [!code highlight]
            child: child, // [!code highlight]
          ), // [!code highlight]
        ), // [!code highlight]
      ],
    );
  }
}
```

## Third-party Examples

### flutter_screenutil

If you are using [`flutter_screenutil`](https://pub.dev/packages/flutter_screenutil) package, you can use the `BuilderAddon` to wrap your use-cases with `ScreenUtilInit` widget.

<Warning>
  [`flutter_screenutil`](https://pub.dev/packages/flutter_screenutil) >= 5.9.2
  doesn't work with Widgetbook as they started using `View.of` instead of
  `MediaQuery.of` in
  [`f7c551a`](https://github.com/OpenFlutter/flutter_screenutil/commit/f7c551acd4cc82460c3a29ec5a7262d6ec678746)
</Warning>

```yaml title=widgetbook/pubspec.yaml
dependencies:
  flutter_screenutil: 5.9.1
  widgetbook: ^{{ versions.widgetbook }}
```

```dart title=widgetbook/lib/main.dart
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:widgetbook/widgetbook.dart';

class WidgetbookApp extends StatelessWidget {
  const WidgetbookApp({super.key});

  @override
  Widget build(BuildContext context) {
    return Widgetbook(
      directories: directories,
      addons: [
        BuilderAddon(
          name: 'ScreenUtil',
          builder: (context, child) {
            return ScreenUtilInit(
              designSize: const Size(375, 812),
              minTextAdapt: true,
              splitScreenMode: true,
              // This is needed to use the workbench [MediaQuery]
              useInheritedMediaQuery: true,
              builder: (context, child) => child!,
              child: child,
            );
          },
        )
      ],
    );
  }
}
```

### accessibility_tools

An alternative to the deprecated `AccessibilityAddon` is to use the
[`accessibility_tools`](https://pub.dev/packages/accessibility_tools) package with the `BuilderAddon` as follows:

```yaml title=widgetbook/pubspec.yaml
dependencies:
  accessibility_tools: ^2.0.0
  widgetbook: ^{{ versions.widgetbook }}
```

```dart title=widgetbook/lib/main.dart
import 'package:accessibility_tools/accessibility_tools.dart';
import 'package:widgetbook/widgetbook.dart';

class WidgetbookApp extends StatelessWidget {
  const WidgetbookApp({super.key});

  @override
  Widget build(BuildContext context) {
    return Widgetbook(
      directories: directories,
      addons: [
        BuilderAddon(
          name: 'Accessibility',
          builder: (context, child) => AccessibilityTools(
            child: child,
          ),
        ),
      ],
    );
  }
}
```
```

### Custom Addon

Source: https://docs.widgetbook.io/addons/custom-addon

```mdx
# Custom Addon

If the built-in addons do not meet your needs, you can create your own custom Addons. This
allows you to extend the functionality of Widgetbook and tailor it to your specific requirements.

## Guide

In this guide, we will be creating a "border addon" that allows you to put a border around your use-case.

### 0. Base structure

Start by creating a new file in your widgetbook project (e.g. `border_addon.dart`).

```dart title=widgetbook/lib/addons/border_addon.dart
import 'package:flutter/widgets.dart';
import 'package:widgetbook/widgetbook.dart';

class BorderSetting {
  const BorderSetting({
    required this.width,
    required this.color,
  });

  final int width;
  final Color color;
}

class BorderAddon extends WidgetbookAddon<BorderSetting> {
  BorderAddon() : super(name: 'Border');

  @override
  List<Field> get fields {
    // TODO
  }

  @override
  BorderSetting valueFromQueryGroup(Map<String, String> group) {
    // TODO
  }

  @override
  Widget buildUseCase(
    BuildContext context,
    Widget child,
    BorderSetting setting,
  ) {
    // TODO
  }
}
```

Now let's start by implementing each of the class members step by step.

### 1. `fields` getter

This is a list of [fields](/essentials/fields) that describe how the addon addon will be represented in both:

- The Widgetbook UI – the "Addons" panel.
- The URL query parameters.

In this case we just need **two fields** to represent the `BorderSetting`:

1. `IntSliderField` - to pick an integer value for `BorderSetting.width`.
1. `ColorField` - to pick a color value for `BorderSetting.color`.

```dart
@override
List<Field> get fields {
  return [
    IntSliderField(
      name: 'width',
      initialValue: 1,
      min: 1,
      max: 10,
    ),
    ColorField(
      name: 'color',
      initialValue: const Color(0xFF000000),
    ),
  ];
}
```

### 2. `valueFromQueryGroup` method

This method is responsible for parsing the query parameters back to the addon setting (i.e. `BorderSetting`). This can be done by using the `valueOf` helper method to extract the values from the query group by giving it **the field's name**.

```dart
@override
BorderSetting valueFromQueryGroup(Map<String, String> group) {
  final width = valueOf<int>('width', group)!;
  final color = valueOf<Color>('color', group)!;

  return BorderSetting(
    width: width,
    color: color,
  );
}
```

### 3. `buildUseCase` method

This method is responsible for building the use-case widget with the addon applied. In this case, we will wrap the child widget with a `Container` that has a border with the given width and color.

```dart
@override
Widget buildUseCase(
  BuildContext context,
  Widget child,
  BorderSetting setting,
) {
  return Container(
    decoration: BoxDecoration(
      border: Border.all(
        color: setting.color,
        width: setting.width.toDouble(),
      ),
    ),
    child: child,
  );
}
```

### 4. Using the Addon

Now that we have implemented the `BorderAddon`, we can use it in our Widgetbook project. To do this, we need to add it to the `addons` list in the `Widgetbook` widget.

```dart title=widgetbook/lib/main.dart
import 'package:flutter/widgets.dart';
import 'package:widgetbook/widgetbook.dart';

import 'addons/border_addon.dart';  // [!code highlight]

class WidgetbookApp extends StatelessWidget {
  const WidgetbookApp({super.key});

  @override
  Widget build(BuildContext context) {
    return Widgetbook(
      // ...
      addons: [BorderAddon()], // [!code highlight]
    );
  }
}
```

## Multi-snapshot Support

Custom addons can be used with [Multi Snapshot Reviews](/cloud/snapshots/multi-snapshot) by implementing the `AddonConfig` interface.

<Info>
  We are using `String colorHex` instead of `Color color` in the `AddonConfig`
  because we cannot convert color to hex in a constant constructor. And the
  constructor needs to be constant to be used in the `cloudAddonsConfigs` map.
</Info>

```dart title=widgetbook/lib/addons/border_addon.dart
import 'package:widgetbook_annotation/widgetbook_annotation.dart';

class BorderAddonConfig extends AddonConfig {
  const BorderAddonConfig(this.width, this.colorHex)
      : super(
          'border', // addon's name in kebab-case
          'width:$width,color:$colorHex', // comma-separated fields' names/values
        );

  final int width;
  final String colorHex;
}
```

```dart title=widgetbook/lib/main.dart
import 'package:flutter/widgets.dart';
import 'package:widgetbook/widgetbook.dart';
import 'package:widgetbook_annotation/widgetbook_annotation.dart';

import 'addons/border_addon.dart'; // [!code highlight]

@App(
  cloudAddonsConfigs: {
    'border black (2)': [
      BorderAddonConfig(2, 'ff000000'), // [!code highlight]
    ],
    'border white (4)': [
      BorderAddonConfig(4, 'ffffffff'), // [!code highlight]
    ],
  },
)
class WidgetbookApp extends StatelessWidget {
  const WidgetbookApp({super.key});

  @override
  Widget build(BuildContext context) {
    return Widgetbook(
      // ...
      addons: [
        BorderAddon(), // [!code highlight]
      ],
    );
  }
}
```
```

### Device Frame Addon

Source: https://docs.widgetbook.io/addons/device-frame-addon

```mdx
# Device Frame Addon

<Warning>
  This addon will be **deprecated** in v3.15. Please use the
  [`ViewportAddon`](/addons/viewport-addon) instead.
</Warning>

The `DeviceFrameAddon` is a powerful tool that allows you to preview your use-cases
in a variety of [device frames](https://pub.dev/packages/device_frame). This is particularly useful for ensuring that your
components look great on different screen sizes and orientations. The addon provides a
set of predefined devices.

<Tabs
  values={[
    { label: "iPhone 13", value: "iphone" },
    { label: "No Device", value: "none" },
  ]}
>
  <TabItem value="iphone">
    <iframe
      src="https://demo.widgetbook.io/#/?path=features/basket/basketscreen/default&preview"
      width="100%"
      height="720px"
    />
  </TabItem>
  <TabItem value="none">
    <iframe
      src="https://demo.widgetbook.io/#/?path=features/basket/basketscreen/default&device={name:None}&preview"
      width="100%"
      height="720px"
    />
  </TabItem>
</Tabs>

## Usage

```dart title=widgetbook/lib/main.dart
class WidgetbookApp extends StatelessWidget {
  const WidgetbookApp({super.key});

  @override
  Widget build(BuildContext context) {
    return Widgetbook(
      // ...
      addons: [
        DeviceFrameAddon( // [!code highlight]
          devices: [ // [!code highlight]
            Devices.ios.iPhoneSE, // [!code highlight]
            Devices.ios.iPhone13, // [!code highlight]
          ], // [!code highlight]
        ), // [!code highlight]
      ],
    );
  }
}
```

<Info>

You can define your own [custom device](https://pub.dev/packages/device_frame#creating-a-custom-generic-device) by using the `DeviceInfo` class:

```dart
DeviceInfo.genericPhone(
  id: 'android-device',
  name: 'Android Device',
  platform: TargetPlatform.android,
  screenSize: const Size(412, 732),
)
```

</Info>

## Order

Since the [order of addons](/addons/overview#order-of-addons) matters. The `DeviceFrameAddon` should be used **before all other addons**.

## Multi-snapshot Support

The `DeviceFrameAddon` is not supported in the [Multi Snapshot Reviews](/cloud/snapshots/multi-snapshot) yet. However, you can use the [`ViewportAddon`](/addons/viewport-addon) to achieve a similar effect.
```

### Grid Addon

Source: https://docs.widgetbook.io/addons/grid-addon

```mdx
# Grid Addon

A utility addon that helps you show grid guidelines behind your use-cases. This is useful for
designers and developers to ensure that their components are aligned correctly and follow the design guidelines.

## Usage

```dart title=widgetbook/lib/main.dart
class WidgetbookApp extends StatelessWidget {
  const WidgetbookApp({super.key});

  @override
  Widget build(BuildContext context) {
    return Widgetbook(
      // ...
      addons: [
        GridAddon(10), // [!code highlight]
      ],
    );
  }
}
```

## Order

Since the [order of addons](/addons/overview#order-of-addons) matters, here are some guidelines to follow when using the `GridAddon`:

Addons that should come before the `GridAddon`:

- `ViewportAddon`
- `DeviceFrameAddon`

Addons that should come after the `GridAddon`:

- `AlignmentAddon`
```

### Inspector Addon

Source: https://docs.widgetbook.io/addons/inspector-addon

```mdx
# Inspector Addon

The `InspectorAddon`, _built on the ['inspector'](https://pub.dev/packages/inspector) package_, enhances Widgetbook by enabling widget property inspection, such as colors, sizes, and padding, directly in the Workbench.

## Usage

```dart title=widgetbook/lib/main.dart
class WidgetbookApp extends StatelessWidget {
  const WidgetbookApp({super.key});

  @override
  Widget build(BuildContext context) {
    return Widgetbook(
      // ...
      addons: [
        InspectorAddon(), // [!code highlight]
      ],
    );
  }
}
```

## Order

Since the [order of addons](/addons/overview#order-of-addons) matters, here are some guidelines to follow when using the `InspectorAddon`:

Addons that should come before the `InspectorAddon`:

- `ViewportAddon`
- `DeviceFrameAddon`
```

### Localization Addon

Source: https://docs.widgetbook.io/addons/localization-addon

```mdx
# Localization Addon

The `LocalizationAddon` in Widgetbook lets developers preview how widgets behave under
different localization settings. This becomes essential when developing applications for a
global audience, accommodating differences in language, text direction, and regional
conventions.

<Tabs
  values={[
    { label: "German", value: "de" },
    { label: "English", value: "en" },
  ]}
>
  <TabItem value="de">
    <iframe
      src="https://demo.widgetbook.io/#/?path=features/basket/basketscreen/default&device={name:None}&locale={name:de}&preview"
      width="100%"
      height="560px"
    />
  </TabItem>
  <TabItem value="en">
    <iframe
      src="https://demo.widgetbook.io/#/?path=features/basket/basketscreen/default&device={name:None}&locale={name:en}&preview"
      width="100%"
      height="560px"
    />
  </TabItem>
</Tabs>

## Usage

<Info>
  This guide assumes that you are using the
  [`flutter_localizations`](https://docs.flutter.dev/ui/accessibility-and-internationalization/internationalization#introduction-to-localizations-in-flutter)
  package. If you are not using it, you can skip steps 1-3.
</Info>

You need to export the `supportedLocales` and `localizationsDelegates` from your app, so that you can use them in Widgetbook. You can do so as follows:

1. Change the `l10n.yaml` file by setting the [`synthetic-package`](https://docs.flutter.dev/ui/accessibility-and-internationalization/internationalization#configuring-the-l10n-yaml-file:~:text=won%27t%20be%20generated.-,synthetic%2Dpackage,-Determines%20whether%20the) option to `false`:

   ```yaml title='l10n.yaml'
   arb-dir: lib/l10n
   template-arb-file: app_en.arb
   output-localization-file: app_localizations.dart
   synthetic-package: false  // [!code highlight]
   ```

1. Update your `.gitignore` file to include the generated localization files:

   ```gitignore title='.gitignore'
   # Generated localization files
   lib/l10n/*.dart
   ```

1. Generate the localization files by running the following command:

   ```bash
   flutter gen-l10n
   ```

1. Add the addon to your Widgetbook app:

   ```dart title=widgetbook/lib/main.dart
   import 'package:my_app/l10n/app_localizations.dart';  // [!code highlight]
   import 'package:widgetbook/widgetbook.dart';

   class WidgetbookApp extends StatelessWidget {
     const WidgetbookApp({super.key});

     @override
     Widget build(BuildContext context) {
       return Widgetbook(
         // ...
         addons: [
           LocalizationAddon( // [!code highlight]
             locales: AppLocalizations.supportedLocales, // [!code highlight]
             localizationsDelegates: AppLocalizations.localizationsDelegates, // [!code highlight]
           ), // [!code highlight]
         ],
       );
     }
   }
   ```

## Order

The order of this addon doesn't matter, you can place it anywhere in the list of addons.

## Multi-snapshot Support

The `LocalizationAddon` is supported in the [Multi Snapshot Reviews](/cloud/snapshots/multi-snapshot) via the `LocalizationAddonConfig`. Here's how to configure it:

```dart title=widgetbook/lib/main.dart
import 'package:my_app/l10n/app_localizations.dart';  // [!code highlight]
import 'package:widgetbook/widgetbook.dart';
import 'package:widgetbook_annotation/widgetbook_annotation.dart';

@App(
  cloudAddonsConfigs: {
    'German': [
      LocalizationAddonConfig('de'), // [!code highlight]
    ],
    'English': [
      LocalizationAddonConfig('en'), // [!code highlight]
    ],
  },
)
class WidgetbookApp extends StatelessWidget {
  const WidgetbookApp({super.key});

  @override
  Widget build(BuildContext context) {
    return Widgetbook(
      // ...
      addons: [
        LocalizationAddon( // [!code highlight]
          locales: AppLocalizations.supportedLocales, // [!code highlight]
          localizationsDelegates: AppLocalizations.localizationsDelegates, // [!code highlight]
        ), // [!code highlight]
      ],
    );
  }
}
```
```

### Introduction to Addons

Source: https://docs.widgetbook.io/addons/overview

```mdx
# Introduction to Addons

Addons in Widgetbook provide a flexible and customizable way to enhance your development environment. They allow you to wrap all your use cases with configurable widgets that can be controlled via Widgetbook's UI.

For example, if you want to center all your use cases using the [`Align`](https://api.flutter.dev/flutter/widgets/Align-class.html) widget, you can use the [`AlignmentAddon`](/addons/alignment-addon) instead of manually wrapping each use case.

```dart title=widgetbook/lib/main.dart
class WidgetbookApp extends StatelessWidget {
  const WidgetbookApp({super.key});

  @override
  Widget build(BuildContext context) {
    return Widgetbook(
      // ...
      addons: [
        // Addons go here...
      ],
    );
  }
}
```

## Order of Addons

The order of addons matters and it will have effect on how the use-case is rendered. The first addon in the list will be the outermost widget, and the last addon will be the innermost widget.

For example, if you are using both [`AlignmentAddon`](/addons/alignment-addon) and [`ViewportAddon`](/addons/viewport-addon), then the `ViewportAddon` should **always come first**, otherwise the `AlignmentAddon` will be aligning the "viewport" widget itself, and not the use-case inside of it.

<Tabs
  values={[
    { label: "✅ Correct Order", value: "correct" },
    { label: "❌ Wrong Order", value: "wrong" },
  ]}
>
  <TabItem value="correct">

    ```dart title=widgetbook/lib/main.dart
    class WidgetbookApp extends StatelessWidget {
      const WidgetbookApp({super.key});

      @override
      Widget build(BuildContext context) {
        return Widgetbook(
          // ...
          addons: [
            ViewportAddon(Viewports.all), // [!code highlight]
            AlignmentAddon() // [!code highlight]
          ],
        );
      }
    }
    ```

    ```text title="Rendered Widget Tree"
    RootWidget
    └── ViewportAddon // [!code highlight]
      └── AlignmentAddon // [!code highlight]
        └── UseCaseRenderer
          └── UseCase
            └── Widget
    ```

  </TabItem>
  <TabItem value="wrong">

    ```dart title=widgetbook/lib/main.dart
    class WidgetbookApp extends StatelessWidget {
      const WidgetbookApp({super.key});

      @override
      Widget build(BuildContext context) {
        return Widgetbook(
          // ...
          addons: [
            AlignmentAddon(), // [!code highlight]
            ViewportAddon(Viewports.all), // [!code highlight]
          ],
        );
      }
    }
    ```

    ```text title="Rendered Widget Tree"
    RootWidget
    └── AlignmentAddon // [!code highlight]
      └── ViewportAddon // [!code highlight]
        └── UseCaseRenderer
          └── UseCase
            └── Widget
    ```

  </TabItem>
</Tabs>
```

### Semantics Addon

Source: https://docs.widgetbook.io/addons/semantics-addon

```mdx
# Semantics Addon

<Warning>
  This addon is currently **experimental**, so any breaking changes can be
  introduced at any minor version.
</Warning>

The Semantics Addon is a powerful tool that allows you to visualize the semantics tree of your widget. This is particularly useful for debugging accessibility issues and ensuring that your app is accessible to all users.

The addon is based on a minimal variant of Flutter's [`SemanticsDebugger`](https://api.flutter.dev/flutter/widgets/SemanticsDebugger-class.html) widget.

## Usage

```dart title=widgetbook/lib/main.dart
class WidgetbookApp extends StatelessWidget {
  const WidgetbookApp({super.key});

  @override
  Widget build(BuildContext context) {
    return Widgetbook(
      // ...
      addons: [
        SemanticsAddon(), // [!code highlight]
      ],
    );
  }
}
```

## Order

Since the [order of addons](/addons/overview#order-of-addons) matters, the `SemanticsAddon` should be used **after all other addons**, as they might be adding some semantics nodes to the tree.

## Multi-snapshot Support

The `SemanticsAddon` is supported in the [Multi Snapshot Reviews](/cloud/snapshots/multi-snapshot) via the `SemanticsAddonConfig`. Here's how to configure it:

```dart title=widgetbook/lib/main.dart
import 'package:widgetbook/widgetbook.dart';
import 'package:widgetbook_annotation/widgetbook_annotation.dart';

@App(
  cloudAddonsConfigs: {
    'Semantics Tree': [
      SemanticsAddonConfig(true), // [!code highlight]
    ],
    'No Semantics Tree': [
      SemanticsAddonConfig(false), // [!code highlight]
    ],
  },
)
class WidgetbookApp extends StatelessWidget {
  const WidgetbookApp({super.key});

  @override
  Widget build(BuildContext context) {
    return Widgetbook(
      // ...
      addons: [
        SemanticsAddon(), // [!code highlight]
      ],
    );
  }
}
```
```

### Text Scale Addon

Source: https://docs.widgetbook.io/addons/text-scale-addon

```mdx
# Text Scale Addon

The `TextScaleAddon` allows developers to adjust the `MediaQuery.textScaler` value to visualize how text scale changes impact layout and rendering.

<Tabs
  values={[
    { label: "x1.5", value: "1.5" },
    { label: "x1", value: "1" },
  ]}
>
  <TabItem value="1.5">
    <iframe
      src="https://demo.widgetbook.io/#/?path=features/basket/basketscreen/default&text-scale={factor:1.5}&device={name:None}&preview"
      width="100%"
      height="560px"
    />
  </TabItem>
  <TabItem value="1">
    <iframe
      src="https://demo.widgetbook.io/#/?path=features/basket/basketscreen/default&text-scale={factor:1}&device={name:None}&preview"
      width="100%"
      height="560px"
    />
  </TabItem>
</Tabs>

## Usage

```dart title=widgetbook/lib/main.dart
class WidgetbookApp extends StatelessWidget {
  const WidgetbookApp({super.key});

  @override
  Widget build(BuildContext context) {
    return Widgetbook(
      // ...
      addons: [
        TextScaleAddon(), // [!code highlight]
      ],
    );
  }
}
```

## Order

Since the [order of addons](/addons/overview#order-of-addons) matters, here are some guidelines to follow when using the `TextScaleAddon`:

Addons that should come before the `TextScaleAddon`:

- `ViewportAddon`
- `DeviceFrameAddon`

## Multi-snapshot Support

The `TextScaleAddon` is supported in the [Multi Snapshot Reviews](/cloud/snapshots/multi-snapshot) via the `TextScaleAddonConfig`. Here's how to configure it:

```dart title=widgetbook/lib/main.dart
import 'package:widgetbook/widgetbook.dart';
import 'package:widgetbook_annotation/widgetbook_annotation.dart';

@App(
  cloudAddonsConfigs: {
    'x1': [
      TextScaleAddonConfig(1), // [!code highlight]
    ],
    'x1.5': [
      TextScaleAddonConfig(1.5), // [!code highlight]
    ],
  },
)
class WidgetbookApp extends StatelessWidget {
  const WidgetbookApp({super.key});

  @override
  Widget build(BuildContext context) {
    return Widgetbook(
      // ...
      addons: [
        TextScaleAddon(), // [!code highlight]
      ],
    );
  }
}
```
```

### Theme Addon

Source: https://docs.widgetbook.io/addons/theme-addon

```mdx
# Theme Addon

The Theme Addon lets you inject a theme widget into your tree, to make your use-cases styled and try them out with different themes.
There are 3 different variants of the Theme Addon, which depend on the theme data and widget you use in your application:

| Addon                 | Theme Data           | Inherited Widget |
| --------------------- | -------------------- | ---------------- |
| `MaterialThemeAddon`  | `ThemeData`          | `Theme`          |
| `CupertinoThemeAddon` | `CupertinoThemeData` | `CupertinoTheme` |
| `ThemeAddon`          | Custom               | Custom           |

<Tabs
  values={[
    { label: "Dark Theme", value: "dark" },
    { label: "Light Theme", value: "light" },
  ]}
>
  <TabItem value="dark">
    <iframe
      src="https://demo.widgetbook.io/#/?path=features/basket/basketscreen/default&device={name:None}&locale={name:en}&theme={name:Dark}&preview"
      width="100%"
      height="720px"
    />
  </TabItem>
  <TabItem value="light">
    <iframe
      src="https://demo.widgetbook.io/#/?path=features/basket/basketscreen/default&device={name:None}&locale={name:en}&theme={name:Light}&preview"
      width="100%"
      height="720px"
    />
  </TabItem>
</Tabs>

## Usage

### Material Theme

If your app is built using `MaterialApp`, then you are probably using `ThemeData` to define your themes. Then `MaterialThemeAddon` is the right choice for you.

```dart title=widgetbook/lib/main.dart
import 'package:my_app/themes.dart'; // For AppThemes
import 'package:widgetbook/widgetbook.dart';

class WidgetbookApp extends StatelessWidget {
  const WidgetbookApp({super.key});

  @override
  Widget build(BuildContext context) {
    return Widgetbook.material(
      // ...
      addons: [
        MaterialThemeAddon( // [!code highlight]
          themes: [ // [!code highlight]
            WidgetbookTheme( // [!code highlight]
              name: 'Light', // [!code highlight]
              data: AppThemes.light(), // [!code highlight]
            ), // [!code highlight]
            WidgetbookTheme( // [!code highlight]
              name: 'Dark', // [!code highlight]
              data: AppThemes.dark(), // [!code highlight]
            ), // [!code highlight]
          ], // [!code highlight]
        ), // [!code highlight]
      ],
    );
  }
}
```

### Cupertino Theme

If your app is built using `CupertinoApp`, then you are probably using `CupertinoThemeData` to define your themes. Then `CupertinoThemeAddon` is the right choice for you.

```dart title=widgetbook/lib/main.dart
import 'package:my_app/themes.dart'; // For AppThemes
import 'package:widgetbook/widgetbook.dart';

class WidgetbookApp extends StatelessWidget {
  const WidgetbookApp({super.key});

  @override
  Widget build(BuildContext context) {
    return Widgetbook.cupertino(
      // ...
      addons: [
        CupertinoThemeAddon( // [!code highlight]
          themes: [ // [!code highlight]
            WidgetbookTheme( // [!code highlight]
              name: 'Light', // [!code highlight]
              data: AppThemes.light(), // [!code highlight]
            ), // [!code highlight]
            WidgetbookTheme( // [!code highlight]
              name: 'Dark', // [!code highlight]
              data: AppThemes.dark(), // [!code highlight]
            ), // [!code highlight]
          ], // [!code highlight]
        ), // [!code highlight]
      ],
    );
  }
}
```

### Custom Theme

If you have a custom theme data and widget or you have a custom logic to build your theme, then `ThemeAddon` is the right choice for you.

<Accordion title="App Custom Theme">

```dart title='lib/theme.dart'
// Custom theme data
class AppThemeData {
  AppThemeData(this.color);

  final Color color;
}

// Custom theme Inherited Widget
class AppTheme extends InheritedWidget {
  const AppTheme({
    required this.data,
    required Widget child,
    super.key,
  }) : super(
          child: child,
        );

  final AppThemeData data;

  // To access the theme's data using AppTheme.of(context)
  static AppThemeData of(BuildContext context) {
    final widget = context.dependOnInheritedWidgetOfExactType<AppTheme>();
    return widget!.data;
  }

  @override
  bool updateShouldNotify(covariant AppTheme oldWidget) {
    return data != oldWidget.data;
  }
}
```

</Accordion>

```dart title=widgetbook/lib/main.dart
import 'dart:ui';
import 'package:my_app/theme.dart'; // For AppTheme
import 'package:my_app/themes.dart'; // For AppThemes
import 'package:widgetbook/widgetbook.dart';

class WidgetbookApp extends StatelessWidget {
  const WidgetbookApp({super.key});

  @override
  Widget build(BuildContext context) {
    return Widgetbook(
      // ...
      addons: [
        ThemeAddon<AppThemeData>( // [!code highlight]
          themes: [ // [!code highlight]
            WidgetbookTheme( // [!code highlight]
              name: 'Light', // [!code highlight]
              data: AppThemes.light(), // [!code highlight]
            ), // [!code highlight]
            WidgetbookTheme( // [!code highlight]
              name: 'Dark', // [!code highlight]
              data: AppThemes.dark(), // [!code highlight]
            ), // [!code highlight]
          ], // [!code highlight]
          themeBuilder: (context, theme, child) { // [!code highlight]
            // Wrap use cases with the custom theme's InheritedWidget // [!code highlight]
            return AppTheme( // [!code highlight]
              data: theme, // [!code highlight]
              child: child, // [!code highlight]
            ); // [!code highlight]
          }, // [!code highlight]
        ), // [!code highlight]
      ],
    );
  }
}
```

## Order

Since the [order of addons](/addons/overview#order-of-addons) matters, here are some guidelines to follow when using the `ThemeAddon`:

Addons that should come before the `ThemeAddon`:

- `ViewportAddon`
- `DeviceFrameAddon`

## Multi-snapshot Support

The `ThemeAddon`s are supported in the [Multi Snapshot Reviews](/cloud/snapshots/multi-snapshot) via the `ThemeAddonConfig`. Here's how to configure it:

```dart title=widgetbook/lib/main.dart
import 'package:widgetbook/widgetbook.dart';
import 'package:widgetbook_annotation/widgetbook_annotation.dart';

@App(
  cloudAddonsConfigs: {
    'Light': [
      ThemeAddonConfig('Light'), // [!code highlight]
    ],
    'Dark': [
      ThemeAddonConfig('Dark'), // [!code highlight]
    ],
  },
)
class WidgetbookApp extends StatelessWidget {
  const WidgetbookApp({super.key});

  @override
  Widget build(BuildContext context) {
    return Widgetbook(
      // ...
      addons: [
        // Your theme addon of choice here... // [!code highlight]
      ],
    );
  }
}
```
```

### Time Dilation Addon

Source: https://docs.widgetbook.io/addons/time-dilation-addon

```mdx
# Time Dilation Addon

A utility addon designed for modifying the scheduler's [`timeDilation`](https://api.flutter.dev/flutter/scheduler/timeDilation.html) property.

## Usage

```dart title=widgetbook/lib/main.dart
class WidgetbookApp extends StatelessWidget {
  const WidgetbookApp({super.key});

  @override
  Widget build(BuildContext context) {
    return Widgetbook(
      // ...
      addons: [
        TimeDilationAddon(), // [!code highlight]
      ],
    );
  }
}
```
```

### Viewport Addon

Source: https://docs.widgetbook.io/addons/viewport-addon

```mdx

# Viewport Addon

The Viewport Addon is an invaluable tool that lets you preview your use-cases on various viewports. These viewports simulate a realistic environment by:

1. Setting width and height boundaries to your use-case.
1. Overriding `MediaQuery`'s `devicePixelRatio` to ensure your component behaves as as expected on different devices.
1. Overriding `ThemeData.platform` to simulate different platforms.

## Usage

```dart title=widgetbook/lib/main.dart
class WidgetbookApp extends StatelessWidget {
  const WidgetbookApp({super.key});

  @override
  Widget build(BuildContext context) {
    return Widgetbook(
      // ...
      addons: [
        ViewportAddon([ // [!code highlight]
          Viewports.none, // [!code highlight]
          IosViewports.iPhone13, // [!code highlight]
          AndroidViewports.samsungGalaxyNote20, // [!code highlight]
          MacosViewports.macbookPro, // [!code highlight]
          WindowsViewports.desktop, // [!code highlight]
          LinuxViewports.desktop, // [!code highlight]
        ]), // [!code highlight]
      ],
    );
  }
}
```

<Info>
  You can use `.all` getter to add all available viewports to the addon:

    ```dart
    ViewportAddon(Viewports.all) // All platforms
    ViewportAddon(IosViewports.all) // Specific platform
    ```

</Info>

## Order

Since the [order of addons](/addons/overview#order-of-addons) matters. The `ViewportAddon` should be used **before all other addons**.

## Multi-snapshot Support

The `ViewportAddon` is supported in the [Multi Snapshot Reviews](/cloud/snapshots/multi-snapshot) via the `ViewportAddonConfig`. Here's how to configure it:

<Info>
  Unlike other `AddonConfig`s, the `ViewportAddonConfig` is defined in
  `widgetbook` package, and not in `widgetbook_annotation` package.
</Info>

```dart title=widgetbook/lib/main.dart
import 'package:widgetbook/widgetbook.dart';
import 'package:widgetbook_annotation/widgetbook_annotation.dart';

@App(
  cloudAddonsConfigs: {
    'iPhone 13': [
      ViewportAddonConfig(IosViewports.iPhone13), // [!code highlight]
    ],
    'iPhone 12': [
      ViewportAddonConfig(IosViewports.iPhone12), // [!code highlight]
    ],
  },
)
class WidgetbookApp extends StatelessWidget {
  const WidgetbookApp({super.key});

  @override
  Widget build(BuildContext context) {
    return Widgetbook(
      // ...
      addons: [
        ViewportAddon([ // [!code highlight]
          IosViewports.iPhone13, // [!code highlight]
          IosViewports.iPhone12, // [!code highlight]
        ]), // [!code highlight]
      ],
    );
  }
}
```
```

### Zoom Addon

Source: https://docs.widgetbook.io/addons/zoom-addon

```mdx
# Zoom Addon

A utility addon designed for zooming in/out of the previewed use-case.

## Usage

```dart title=widgetbook/lib/main.dart
class WidgetbookApp extends StatelessWidget {
  const WidgetbookApp({super.key});

  @override
  Widget build(BuildContext context) {
    return Widgetbook(
      // ...
      addons: [
        ZoomAddon(), // [!code highlight]
      ],
    );
  }
}
```

## Order

Since the [order of addons](/addons/overview#order-of-addons) matters, here are some guidelines to follow when using the `ZoomAddon`:

Addons that should come before the `ZoomAddon`:

- `ViewportAddon`
- `DeviceFrameAddon`
- `AlignmentAddon`

## Multi-snapshot Support

The `ZoomAddon` is supported in the [Multi Snapshot Reviews](/cloud/snapshots/multi-snapshot) via the `ZoomAddonConfig`. Here's how to configure it:

```dart title=widgetbook/lib/main.dart
import 'package:widgetbook/widgetbook.dart';
import 'package:widgetbook_annotation/widgetbook_annotation.dart';

@App(
  cloudAddonsConfigs: {
    'x1': [
      ZoomAddonConfig(1), // [!code highlight]
    ],
    'x2': [
      ZoomAddonConfig(2), // [!code highlight]
    ],
  },
)
class WidgetbookApp extends StatelessWidget {
  const WidgetbookApp({super.key});

  @override
  Widget build(BuildContext context) {
    return Widgetbook(
      // ...
      addons: [
        ZoomAddon(), // [!code highlight]
      ],
    );
  }
}
```
```

### Widgetbook Cloud Reviews

Source: https://docs.widgetbook.io/cloud/reviews

```mdx
# Widgetbook Cloud Reviews

> We are already saving 20% of our time.
> – [1KOMMA5°](https://1komma5grad.com/de) _([source](https://youtu.be/Du5yVtnmNzg?t=1759))_

Widgetbook Cloud Reviews not only help you introduce **UI Reviews** into your workflow, but also enable you to **automatically detect UI regressions** with zero configuration (i.e., without writing any golden tests). It provides a structured review process to ensure that all **UI changes are intentional** and match design expectations.

<Image theme="light" src="/assets/cloud/review.light.png" />
<Image theme="dark" src="/assets/cloud/review.dark.png" />

## Tutorial Video

<YouTube id="CalXXyThDi4" />

## Code Review vs. UI Review

In a code review, you look for bugs, performance issues, and code quality. 
In a UI review, you look for visual differences between the design and the implementation. 
Both types of reviews are important to ensure the code is correct and the design is implemented as expected.

|                | Code Review                                        | UI Review                                                                         |
| -------------- | -------------------------------------------------- | --------------------------------------------------------------------------------- |
| **Purpose**    | To find bugs, performance issues, and code quality | To find visual differences between the design and the implementation              |
| **Comparison** | **Code changes** between `base` and `head` commits | **Visual differences** between `base` and `head` [builds](/cloud/builds/overview) |
| **Tool**       | Version Control Systems (e.g. GitHub, GitLab)      | Widgetbook Cloud                                                                  |

## UI Review Workflow

<Info>
**Prerequisite:** Make sure you have completed the setup of Widgetbook Cloud for 
[GitHub](/cloud/guides/github/upload),
[GitLab](/cloud/guides/gitlab/upload), 
[Azure DevOps](cloud/guides/azure/upload), or
[Bitbucket](cloud/guides/bitbucket/upload).
</Info>

1. Submit a pull request (PR) with your changes using your version control system (e.g., GitHub, GitLab).
1. Widgetbook Cloud will **automatically** create a review for your PR and post the URL in your git PR as a **commit status**.
1. Reviewers should review the visual differences (if any) and either **approve** the changes or **request changes**.
1. The review status will be **reflected** in the commit status of your git PR.
1. If changes are requested, the commit status will **block your PR from being merged** until the requested changes are applied. (You can disable this behavior in your project settings.)

## Empower UI Reviews

To give reviewers more power, you can use the following features:

1. [**Figma Reviews**](/cloud/reviews/figma): Compare the implementation with the Figma design.
1. [**Multi Snapshot Reviews**](/cloud/snapshots/multi-snapshot): Generate snapshots for each use case in multiple configurations (e.g., themes, locales, devices) to ensure UI consistency across all configurations.
```

### Workspaces

Source: https://docs.widgetbook.io/cloud/workspaces

```mdx
# Workspaces

The first step in getting started with Widgetbook Cloud is setting up a workspace for your team.

<Image src="/assets/cloud/workspaces/overview.png" zoom />

## Overview

A workspace serves as the central hub for all your projects and allows you to manage the team members that have access to the builds, pull requests, and reviews within the workspace. 
We recommend keeping all organizational efforts within a single workspace, as this aligns with how the product is designed conceptually.

## Workspace settings

<Warning>Only an owner of a workspace can access workspace settings.</Warning>

Navigate to the settings by selecting the workspace and clicking `Settings`. You can access the following setting categories and operations:

General
- Change the workspace name
- Delete the workspace

Members
- Add or remove members to the workspace
- Change the role of a member

Billing
- See and manage your current plan
- Investigate your usage

### Delete workspace

Deleting a workspace includes deleting build, pull request, and review data. 
The owner can delete the workspace under `Settings > General > Delete workspace`.
This action is not reversible.

## Multiple workspaces

You have the option to create several workspaces under a single Widgetbook account (e.g., associated with the same email). 
For those using Widgetbook Cloud for both work and personal projects, we suggest creating separate Widgetbook Cloud accounts, each linked to a different email address.

To add a workspace to your account:
1. Click on the workspace selector in the top left corner.
2. Click on "Create workspace".
3. You will be asked to provide a workspace name.

## FAQ

<Accordion title="How can I make my workspace or project public?">
As of now, it is not possible to make a workspace or project public. 
We are planning to add this feature soon.
</Accordion>
```

### Header Widget

Source: https://docs.widgetbook.io/configure/header-widget

```mdx
# Header Widget

You can add a custom header to the navigation panel by providing a `header` parameter to the `Widgetbook` constructor.
This can be used for branding or additional information.

```dart
@override
Widget build(BuildContext context) {
  return Widgetbook(
    header: const MyCustomHeaderWidget(),
    // ...
  );
}
```
```

### Home Widget

Source: https://docs.widgetbook.io/configure/home-widget

```mdx
# Home Widget

The home widget is a widget that is shown on startup when no use-case is selected.
This widget does not inherit from the `appBuilder` or the `addons`;
Meaning that if `Theme.of(context)` is called inside this widget, then it will use Widgetbook's `lightTheme` or `darkTheme`,
and not the `Theme` from the `appBuilder` or the `ThemeAddon`.

```dart
@override
Widget build(BuildContext context) {
  return Widgetbook(
    home: const MyCustomHomeWidget(),
    // ...
  );
}
```
```

### Initial Route

Source: https://docs.widgetbook.io/configure/initial-route

```mdx
# Initial Route

Initial routes can be used to pick the home page that is used on first launch by providing a `initialRoute` parameter to the `Widgetbook` constructor.

<Info>
  To easily get the exact route for a use-case, run your Widgetbook project on
  web, and copy the value of the URL, then paste it as the value of the
  `initialRoute` parameter.
</Info>

```dart
@override
Widget build(BuildContext context) {
  return Widgetbook(
    initialRoute: '?path=introduction/home-use-case',
    // ...
  );
}
```
```

### Leaf Components

Source: https://docs.widgetbook.io/configure/leaf-components

```mdx
# Leaf Components

When a component has a single use-case, it's called a leaf component. By default, Widgetbook hides the use-case of the leaf component and displays the component directly in the navigation tree. This helps to declutter the navigation tree and make it easier to navigate.

To enable or disable the leaf component behavior, you can use the `enableLeafComponent` parameter in the `Widgetbook` constructor.

```dart
@override
Widget build(BuildContext context) {
  return Widgetbook(
    enableLeafComponent: false, // defaults to true
    // ...
  );
}
```
```

### Root Widget

Source: https://docs.widgetbook.io/configure/root-widget

```mdx
# Root Widget

All use-cases are wrapped in an root widget (i.e. an `App` widget) to provide a context for the use-cases to render in.

```tree
RootWidget
└── Addon 1
  └── Addon 2
    └── UseCaseRenderer
      └── UseCase
        └── Widget
```

Here are the available constructors for the root widgets:

| Constructor            | Root Widget    |
| ---------------------- | -------------- |
| `Widgetbook.material`  | `MaterialApp`  |
| `Widgetbook.cupertino` | `CupertinoApp` |
| `Widgetbook`           | Custom Widget  |

You can use a custom root widget by providing an `appBuilder` function to the `Widgetbook` constructor.
This is helpful if you need to wrap your app widget (i.e. `MaterialApp`, `CupertinoApp`) with a custom widget.

<Info>
  If you want to wrap your use-case in a custom widget, you can also use the
  [Addons API](/addons/overview), which provides some more flexibility.
</Info>

```dart
@override
Widget build(BuildContext context) {
  return Widgetbook(
    // ...
    appBuilder: (context, child) {
      return AwesomePackage(
        child: MaterialApp(
          debugShowCheckedModeBanner: false,
          home: child,
        )
      );
    }
  );
}
```
```

### Scroll Behavior

Source: https://docs.widgetbook.io/configure/scroll-behavior

```mdx
# Scroll Behavior

When running Widgetbook on web or desktop platforms, you might want to customize the scroll behavior to allow dragging with the mouse. This can be useful if you are building widgets that require drag interactions (e.g. [`DraggableScrollableSheet`](https://api.flutter.dev/flutter/material/DraggableScrollableSheet-class.html)).

You can customize the scroll behavior by providing a `scrollBehavior` to the `Widgetbook` constructors.

```dart
@override
Widget build(BuildContext context) {
  return Widgetbook(
    // ...
    scrollBehavior: const MaterialScrollBehavior().copyWith(
      dragDevices: {
        PointerDeviceKind.touch,
        PointerDeviceKind.mouse,
      },
    ),
  );
}
```
```

### Use-cases Navigation Path

Source: https://docs.widgetbook.io/configure/use-case-nav-path

```mdx
# Use-cases Navigation Path

By default, use-cases have a navigation path in Widgetbook that is based on where their component file is located.
There are multiple ways to customize the navigation path either globally or locally.

<Info>
  The component name (e.g. `Button`) and the use-case name (e.g. `Primary`) are
  concatenated at the end of the path.

```
[ Nav Path ] / [ Component Name ] / [ Use-case Name ]
```

</Info>

## Using `nav_path_mode` option

You can **globally** change the behavior of the navigation path generation for all use-cases by providing a `nav_path_mode` option in `build.yaml` as follows:

| Option Value          | Navigation Path Based on                                           |
| --------------------- | ------------------------------------------------------------------ |
| `component` (default) | The component _(i.e. Widget)_ file.                                |
| `use-case`            | The use-case file _(i.e. where the `@UseCase` annotation is used)_ |

```yaml title="build.yaml"
targets:
  $default:
    builders:
      widgetbook_generator:use_case_builder:
        options:
          nav_path_mode: use-case
```

## Using `@UseCase.path` parameter

To provide a custom navigation path for a **single** use-case, you can use the `path` parameter in the `@UseCase` annotation.

```dart
import 'package:your_app/widgets/button.dart';

// Navigation path: `widgets/Button/Primary`
@widgetbook.UseCase(
  name: 'Primary',
  type: Button
)
Widget buildPrimaryButton() {
  return Button();
}
```

<Info>
  If you wrap a folder name in **square brackets**, it will be treated as a
  **category** in the navigation path.
</Info>

```dart
import 'package:your_app/components/button.dart';

// Navigation path: `[Interactions]/buttons/Button/Primary`
@widgetbook.UseCase(
  name: 'Primary',
  type: Button,
  path: '[Interactions]/buttons',
)
Widget buildPrimaryButton() {
  return Button();
}
```
```

### Widgetbook's UI Theme

Source: https://docs.widgetbook.io/configure/widgetbook-theme

```mdx
# Widgetbook's UI Theme

If you want to change some Widgetbook's UI colors, or want to enforce a specific theme mode, you can do so using these parameters:

| Parameter    | Description                                                                                                                       |
| ------------ | --------------------------------------------------------------------------------------------------------------------------------- |
| `lightTheme` | [`ThemeData`](https://api.flutter.dev/flutter/material/ThemeData-class.html) that is used when `themeMode` is `ThemeMode.light` |
| `darkTheme`  | [`ThemeData`](https://api.flutter.dev/flutter/material/ThemeData-class.html) that is used when `themeMode` is `ThemeMode.dark`. |
| `themeMode`  | Controls which [`ThemeMode`](https://api.flutter.dev/flutter/material/ThemeMode.html) to use. Defaults to `ThemeMode.system`.     |

```dart
@override
Widget build(BuildContext context) {
  return Widgetbook(
    lightTheme: ThemeData.light(), // Custom light theme
    darkTheme: ThemeData.dark(),   // Custom dark theme
    themeMode: ThemeMode.light,    // Forcing light mode
    // ...
  );
}
```
```

### Adding an Example

Source: https://docs.widgetbook.io/contribution/adding-example

```mdx
# Adding an Example

Examples serve as practical demonstrations of what can be achieved with the
project. Here's how to contribute an example:

1. **Directory Structure**: Save your example in the `examples` directory.
   Ensure the code is organized, well-documented, and easy to follow.
2. **Continuous Integration (CI) Checks**: Integrate necessary CI checks for
   your example. This ensures that as the main codebase evolves, your example
   remains functional. CI checks can include building, testing, and other
   validations.

3. **README**: Consider adding a README file within your example directory
   explaining what the example demonstrates, any special instructions, or other
   relevant information.
```

### Code Reviews

Source: https://docs.widgetbook.io/contribution/code-reviews

```mdx
# Code Reviews

The review process is an essential part of maintaining and improving the overall
quality of the project. Here's what to expect:

1. **Review Phase**: Once your PR is submitted, it enters the review queue. One
   or more maintainers or experienced contributors will review your code.
2. **Feedback Loop**: Reviewers might provide feedback, asking for changes,
   improvements, or clarifications. This isn't a critique of your skills but an
   essential step to ensure the project's integrity and consistency.
3. **Adjustments**: You might need to adjust your code based on the feedback.
   Update your PR accordingly and notify reviewers once the changes are made.
4. **Approval**: Once all concerns are addressed and reviewers are satisfied,
   your PR will be approved.
5. **Merging**: An approved PR will merge into the project.

_Note_: Patience is key during the review process. Everyone's primary goal is to
ensure the best quality for the project.

## Community

Open-source thrives on collaboration and open dialogue. If you have questions,
require clarifications, or wish to discuss certain features or changes:

- **Join the Conversation**: Head over to our
  [Discord](https://discord.gg/zT4AMStAJA) channel. Engage with other
  contributors, ask questions, share insights, or seek feedback.

- **Stay Updated**: Platforms like Discord often host announcements, discussions
  about upcoming features, or even general project roadmap.

## Licensing

It's essential to understand the legal aspect of your contributions:

- **License Agreement**: By contributing, you agree that your submissions to
  Widgetbook will be licensed under the project's
  [MIT license](https://github.com/widgetbook/widgetbook/blob/main/packages/widgetbook/LICENSE).
  This ensures a uniform licensing structure and protects contributors and the
  project.
```

### Getting Started

Source: https://docs.widgetbook.io/contribution/get-started

```mdx
# Getting Started

Setting up your environment correctly is crucial. This ensures that you can
develop features or fix bugs in an environment that mimics the actual
development conditions, reducing inconsistencies and issues down the line.
Here's a step-by-step guide to get you started:

1. **Fork the Repository:** Before you make any changes, you'll need your copy
   of the Widgetbook repository. You can freely experiment without affecting the
   original project by forking the repository. To do this:

   - Visit the
     [Widgetbook repository](https://github.com/widgetbook/widgetbook).
   - Click on the "**Fork**" button at the top right.
   - Once forked, you'll have your copy under your GitHub account.

   _Tip:_ Regularly sync your fork with the main repository to stay updated with
   the latest changes. This helps in avoiding merge conflicts later on. Check
   out [Synchronize Your Fork](/contribution/sync-your-fork).

2. **Clone Your Fork:** With your fork ready, clone it to your local machine to
   start development. Replace `<YOUR_USERNAME>` with your actual GitHub
   username:

   ```bash
   git clone https://github.com/<YOUR_USERNAME>/widgetbook.git
   ```

   This command creates a local copy of your forked Widgetbook repository. You
   can navigate, make changes, and test them on this local copy.

3. **Install Melos:** `Melos` is a tool that streamlines working with
   mono-repositories (repositories containing multiple projects/packages).
   Widgetbook leverages `Melos` to efficiently manage its various packages:

   ```bash
   dart pub global activate melos
   ```

   By activating Melos, you're setting yourself up to easily manage
   dependencies, run scripts, and more across all the packages in Widgetbook.

4. **Bootstrap the Repository:** Setting up the repository involves multiple
   steps, including installing dependencies, bootstrapping, and generating
   necessary files:

   - Navigate to the cloned repository:

     ```bash
     cd widgetbook
     ```

   - Fetch and install dependencies for the project:

     ```bash
     flutter pub get
     ```

   - Using Melos, bootstrap the project. This step links inter-dependent
     packages in the repo, ensuring they work together seamlessly:

     ```bash
     melos bootstrap
     ```

   - Generate any necessary build files:

     ```bash
     melos generate
     ```

   _Note:_ If at any point you encounter issues, refer to Widgetbook's official
   documentation or seek help from the community on platforms like Discord or
   GitHub Discussions.

5. **Contribution Workflow**: Now that you are ready, let's dive into how to
   send a good PR. Check out [Contribution Workflow](/contribution/workflow).
```

### PR Title Conventions

Source: https://docs.widgetbook.io/contribution/pr-conventions

```mdx
# PR Title Conventions

Well-structured commit messages provide a clear project history and make it
easier for maintainers and contributors to understand the changes. We use the
[conventional commits](https://www.conventionalcommits.org/en/v1.0.0/) format to
ensure consistency across all commits. Here's how you can structure your commit
messages:

## Commit Message Structure

A commit message using the conventional commit format looks like this:

```bash
<type>(<scope>): <subject>
```

Where:

- `<type>` classifies the kind of change being made.
- `<scope>` (optional) describes where the change was made.
- `<subject>` is a brief description of the change.

## Types

Here are the types you can use to classify your changes:

| Type       | Description                                                         |
| ---------- | ------------------------------------------------------------------- |
| `feat`     | A new feature.                                                      |
| `fix`      | A bug fix.                                                          |
| `refactor` | A code change that neither fixes a bug nor adds a feature.          |
| `docs`     | Documentation-only changes.                                         |
| `test`     | Adding or updating tests.                                           |
| `chore`    | Routine tasks like updating dependencies or other non-code changes. |
| `ci`       | Changes related to Continuous Integration.                          |

## Scopes

Scopes help in pinpointing where exactly in the codebase the change happened:

| Scope        | Description                                            |
| ------------ | ------------------------------------------------------ |
| _No scope_   | General changes to the core `widgetbook` package.      |
| `cli`        | Changes specific to the `widgetbook_cli` package.      |
| `annotation` | Modifications in the `widgetbook_annotation` package.  |
| `generator`  | Updates concerning the `widgetbook_generator` package. |
| `release`    | Commits related to version releases.                   |

## Example

Following the convention, a commit that adds a new command to the CLI package
would be:

```bash
  _Example:_ `feat(cli): add new CLI command`
```

By adhering to these conventions, you make the project's history more readable
and accessible, making it easier for maintainers and contributors to navigate
through changes.
```

### Widgetbook Quick Contribution Guide

Source: https://docs.widgetbook.io/contribution/quick-guide

```mdx
# Widgetbook Quick Contribution Guide

## Ways to Contribute

1. Develop new features.
2. Fix bugs.
3. Improve test coverage.
4. Enhance documentation.
5. Provide examples.

## Getting Started

1. Fork the Widgetbook repository.
2. Clone your fork:
   `git clone https://github.com/<YOUR_USERNAME>/widgetbook.git`
3. Install Melos: `dart pub global activate melos`
4. Bootstrap the repository:

   ```bash
   cd widgetbook
   flutter pub get
   melos bootstrap
   melos generate
   ```

## Contribution Workflow

1. Synchronize your fork with the main repo.
2. Create a new branch: `git checkout -b your-branch-name`
3. Implement and test your changes.
4. Format code: `dart fmt -w .`
5. Analyze code: `dart analyzer --fatal-infos --fatal-warnings .`
6. Commit your changes with a meaningful message.
7. Update or add documentation as needed.
8. Ensure all tests pass: `flutter test`
9. Sign the
   [CLA](https://docs.google.com/forms/d/e/1FAIpQLScuRfjUzENsLsmQgqZlGLxMKbFi7zuXoPARyXytoyQrq7ntUw/viewform).
10. Submit a PR to the main repository.

## PR Title Conventions

- Follow [conventional commits](https://www.conventionalcommits.org/en/v1.0.0/).
- Use types like `feat`, `fix`, and `docs`.
- Include scopes such as `cli` or `annotation`.

  _Example:_ `feat(cli): add new CLI command`

## Code Reviews & Community

1. Be patient during the PR review phase.
2. For examples, place in the `examples` directory and add necessary CI checks.
3. Join the Widgetbook community on [Discord](https://discord.gg/zT4AMStAJA).

## Licensing

- All contributions will be under Widgetbook's
  [MIT license](https://github.com/widgetbook/widgetbook/blob/main/packages/widgetbook/LICENSE).

---

Remember, while this guide provides a quick overview, always refer to the
detailed contribution guidelines when in doubt. Happy coding! 🌟
```

### Synchronize Your Fork

Source: https://docs.widgetbook.io/contribution/sync-your-fork

```mdx
# Synchronize Your Fork

While working on a forked repository, it's crucial to keep it updated with the
original repository. This ensures that you're always working on the latest
codebase, which can significantly reduce potential merge conflicts when you're
ready to submit your contributions.

## Setting Up the Upstream

First, you need to specify a new remote `upstream` repository that will be
synced with the fork. If you haven't already, add the original repository (in
this case, Widgetbook) as the `upstream`:

```bash
git remote add upstream https://github.com/widgetbook/widgetbook.git
```

You can verify that the upstream repository has been added:

```bash
git remote -v
```

You should see both your `origin` (which points to your fork) and the `upstream`
(which points to the original repository).

## Syncing the Fork

Once you've set up the `upstream`, you can synchronize your forked repository:

1. Ensure you're on your main branch:

   ```bash
   git checkout main
   ```

2. Fetch the branches and commits from the upstream:

   ```bash
   git fetch upstream
   ```

3. Merge the changes from the `upstream/main` into your local `main` branch:

   ```bash
   git merge upstream/main
   ```

4. At this point, your local `main` branch is synchronized with the original
   repository's `main` branch. To update your fork on GitHub, push your changes:

   ```bash
   git push origin main
   ```

_Note:_ Always make sure your fork is synchronized before starting any new work.
This ensures a smoother experience when creating pull requests later on.

---

By regularly synchronizing your fork, you ensure a more harmonious development
process and reduce potential hurdles when integrating your contributions with
the main project.
```

### Ways to Contribute

Source: https://docs.widgetbook.io/contribution/ways-to-contribute

```mdx
# Ways to Contribute

Contributions to Widgetbook aren't limited to just code. We value a diverse set
of contributions that can help the project thrive and benefit the community at
large. Here are some ways you can make a difference:

1. **[Developing New Features](#developing-new-features):** Enhance the platform
   with new functionalities. This might be a new widget, improved integration
   with other tools, or even introducing a novel capability that could
   revolutionize how users interact with Widgetbook.

   _Example_: Introducing a "dark mode" for the Widgetbook interface.

2. **Fixing Bugs:** Identify issues, glitches, or problems with the existing
   codebase and propose fixes. These contributions help stabilize the platform
   and ensure a seamless experience for users.

   _Example_: Resolving an issue where Widgetbook crashes when loading a
   specific type of widget.

3. **Increasing Test Coverage:** Write new or improve existing tests. High test
   coverage ensures that the codebase remains stable and any further changes
   don't introduce regressions.

   _Example_: Adding tests to verify the behavior of newly introduced widgets
   under various scenarios.

4. **Improving Documentation:** Write, enhance, or clarify the official
   Documentation. A well-documented project helps newcomers get started, and
   experts to dive deep.

   _Example_: Crafting a detailed guide on how to integrate Widgetbook with
   other popular Flutter tools.

5. **Providing Examples:** Share practical use-cases or scenarios. Examples
   often act as starting points and provide clarity on the application of
   certain features. Check out [Adding Examples](/contribution/adding-example) doc
   page for more information.

   _Example_: Creating an example showcasing how to use Widgetbook for managing
   state in a complex Flutter app.

## Developing New Features

Building new features is exciting! But, to ensure that your efforts are in line
with the project's goals and vision, consider the following steps:

1. **Research First:** Begin by browsing the
   [existing enhancement issues](https://github.com/widgetbook/widgetbook/labels/enhancement).
   This will give you a sense of what's being considered and where you can pitch
   in. If you don't see your proposed feature, don't get disheartened.

   _Example_: Let's say you want to add a "drag and drop" feature for widgets
   within Widgetbook. Check if there's an existing issue or discussion around
   it.

2. **Propose Your Feature:** If your idea isn't listed,
   [create a new issue](https://github.com/widgetbook/widgetbook/issues/new).
   Clearly describe the feature, its benefits, potential challenges, and perhaps
   even an essential roadmap or plan on how you envision building it.

   _Example_: For the "drag and drop" feature, you can describe how it would
   improve user workflows, any libraries or tools you plan to use, and potential
   UI/UX mockups.

3. **Express Intent:** Once your issue is created or if you're picking up an
   existing one, comment on it. This informs the community and maintainers that
   you're interested in working on this feature, avoiding duplicated efforts.

   _Example_: A simple comment like, "I'm interested in building this feature
   and have some ideas on how to go about it. Any feedback or suggestions before
   I start?" can kick off the collaboration.

4. **Align with Maintainers:** Get feedback from project maintainers Before
   diving deep into coding. Their insights will be invaluable in shaping the
   direction and ensuring the feature integrates well with the existing
   ecosystem.

   _Example_: After some discussions, maintainers might suggest specific design
   patterns to follow, libraries to use, or even share potential pitfalls to
   watch out for.
```

### Contribution Workflow

Source: https://docs.widgetbook.io/contribution/workflow

```mdx
# Contribution Workflow

Contributing to a project involves a series of steps to ensure the changes are
consistent with the existing codebase and the project's overall goals. Here's a
detailed walkthrough of the process:

## Preparation

### 1. Stay Updated

- Before starting any new work, ensure you have the latest updates from the main
  project.
- Synchronize your fork with the main repository. If you're unsure, refer to the
  section on [Synchronizing Your Fork](/contribution/sync-your-fork).

### 2. Create a Branch

- Always work on a new branch for every new feature or bug fix. This keeps your
  working directory clean and allows you to work on multiple features
  simultaneously.

  ```bash
  git checkout -b feature/my-new-feature
  ```

  _Tip:_ Use descriptive branch names. It helps in understanding the branch's
  purpose at a glance.

## Making Changes

### 1. Test Your Code

- Relevant tests should accompany every contribution. This ensures that your
  changes work as expected and don't introduce bugs.

  _Example:_ If you've added a new widget, create tests to verify its behavior
  under various conditions.

### 2. Implement Changes

- Write clean, well-commented code and adhere to the project's coding standards.
- If you're unsure about a particular approach, discuss it in the issue comments
  or the project's community channels.

### 3. Code Formatting

- Maintain a consistent coding style across the project using Dart's formatting
  tool.

  ```bash
  dart format -w .
  ```

### 4. Analyze Your Code

- Ensure that your code adheres to Dart's best practices and has no potential
  issues.

  ```bash
  dart analyze --fatal-infos --fatal-warnings .
  ```

## Before Submission

### 1. Commit Properly

- Group-related changes into individual commits.
- Use clear and concise commit messages describing the changes.

  _Example:_ Instead of "fix bugs", use "fix null pointer exception in WidgetRenderer".

### 2. Documentation

- Enhance the documentation if you've introduced new features, made changes to
  the public API, or feel that a particular aspect of the project needs clearer
  instructions.

### 3. Testing

- Before submitting your changes, ensure all existing tests pass. Run:

  ```bash
  flutter test
  ```

### 4. Sign the CLA

- Before your changes can be merged, you need to sign the
  [Contributor License Agreement](https://docs.google.com/forms/d/e/1FAIpQLScuRfjUzENsLsmQgqZlGLxMKbFi7zuXoPARyXytoyQrq7ntUw/viewform).
  This is a standard procedure for open-source contributions to protect both the
  contributor and the project.

## Submitting Changes

### 1. PR Creation

- Push your branch to your fork and create a pull request (PR) against the main
  Widgetbook repository.

  _Tip:_ In the PR description, provide a detailed overview of the changes and
  their motivations, and if they're related to any existing issues, link those.

### 2. PR Conventions

- Adhere to the project's PR title and description conventions. If the project
  follows a specific format or template, ensure your PR respects that.

### 3. Verify CI Status

- After creating a PR, automated Continuous Integration (CI) checks will run.
  Ensure they all pass. If any checks fail, review and address the reported
  issues.

<Info>
	Check out [Adding Examples](/contribution/adding-example) doc page for more
	information.
</Info>
```

### Assets

Source: https://docs.widgetbook.io/essentials/assets

```mdx
# Assets

<Info>
  If you have a separate package for your design system with its assets, you can
  skip this guide, because the assets are already included in a separate
  package.
</Info>

If your app have assets, and you want to display them in Widgetbook, you need to set the `package` parameter in the `Image.asset` constructor.
But doing so will make the image not display in your app. To fix this, you need to convert your assets folder into a package as follows.

1. Add a `pubspec.yaml` file in the root of your `assets` folder

   <Info> No `dependencies` are needed</Info>

   ```yaml
   name: assets
   description: >
     Holds the assets for the app. The assets are included in a
     separate package, to be able to share it with Widgetbook app.

   version: 0.0.0
   publish_to: none

   environment:
     sdk: ">=3.1.0 <4.0.0"

   flutter:
     assets:
       - .
   ```

1. Add the `assets` package to your app and widgetbook `pubspec.yaml` files

   ```yaml title="pubspec.yaml"
   dependencies:
     assets:
       path: assets
   ```

   ```yaml title="widgetbook/pubspec.yaml"
   dependencies:
     assets:
       path: ../assets
   ```

1. Use the `package` parameter in the `Image.asset` constructor whenever you want to display an image asset in your app

   ```dart
   Image.asset(
     'assets/image.png',
     package: 'assets',
   )
   ```
```

### Embedding

Source: https://docs.widgetbook.io/essentials/embedding

```mdx
# Embedding

## Preview Mode

If you want to embed Widgetbook in your documentation, but you want to hide Widgetbook's UI, you can do so by adding the **`preview` query parameter** to the URL of your hosted Widgetbook.

```url
https://widgetbook.acme.com/#/?path=my/use-case/path/default&preview
```

For example here's an embedding of [our demo app](https://demo.widgetbook.io/):

```diff
https://demo.widgetbook.io/#/?path=features/about/aboutscreen/default // [!code --]
https://demo.widgetbook.io/#/?path=features/about/aboutscreen/default&preview // [!code ++]
```

<iframe
  src="https://demo.widgetbook.io/#/?path=features/about/aboutscreen/default&preview"
  width="100%"
  height="720px"
/>

<br />
<Info>
  You can host your Widgetbook using [Widgetbook Cloud
  Builds](/cloud/builds/overview).
</Info>

## Customizing Panels

You may want to show or hide certain panels in the embedded Widgetbook. You can do this by adding the **`panels` query parameter** to the URL of your hosted Widgetbook.

<Warning>
  The `panels` query parameter is not compatible with the `preview` query
  parameter. If you use both, the `panels` parameter will be ignored.
</Warning>

```url
# Knobs panel only
https://widgetbook.acme.com/#/?path=my/use-case/path/default&panels=knobs
```

```url
# Knobs and Addons panels
https://widgetbook.acme.com/#/?path=my/use-case/path/default&panels=knobs,addons
```

```url
# Navigation panel only
https://widgetbook.acme.com/#/?path=my/use-case/path/default&panels=navigation
```
```

### Fields

Source: https://docs.widgetbook.io/essentials/fields

```mdx
# Fields

Field types form the backbone of Addons and Knobs in Widgetbook. These types define the
data that can be manipulated within the Widgetbook environment, enabling users to tweak
settings and see their impact in real-time. Each field type corresponds to a different
data input and is represented by a suitable user interface element.

| Field                    | Type          | Widget                                                 |
| ------------------------ | ------------- | ------------------------------------------------------ |
| `BooleanField`           | `bool`        | `Switch`                                               |
| `IntInputField`          | `int`         | `TextField`                                            |
| `IntSliderField`         | `int`         | `Slider`                                               |
| `DoubleInputField`       | `double`      | `TextField`                                            |
| `DoubleSliderField`      | `double`      | `Slider`                                               |
| `StringField`            | `String`      | `TextField`                                            |
| `DurationField`          | `Duration`    | `TextField`                                            |
| `DateTimeField`          | `DateTime`    | `TextField` (with `showDatePicker` & `showTimePicker`) |
| `ColorField`             | `Color`       | `ColorPicker`                                          |
| `IterableSegmentedField` | `Iterable<T>` | `SegmentedButton<T>`                                   |
| `ObjectDropdownField`    | `T`           | `DropdownMenu<T>`                                      |
| `ObjectSegmentedField`   | `T`           | `SegmentedButton<T>`                                   |

Each field type makes Widgetbook a flexible and powerful tool for Flutter developers. They
allow for real-time changes and visualization of various widget properties, thus enabling
interactive development and testing.
```

### Fonts

Source: https://docs.widgetbook.io/essentials/fonts

```mdx
# Fonts

If you are using [`google_fonts`](https://pub.dev/packages/google_fonts) package to load custom fonts in your app, you need to pre-load the fonts in Widgetbook, to avoid visual font swaps.

```dart title=widgetbook/lib/main.dart
Future<void> main() async {
  // TODO: replace `lato` with your desired font
  WidgetsFlutterBinding.ensureInitialized(); // [!code highlight]
  await GoogleFonts.pendingFonts([GoogleFonts.lato()]); // [!code highlight]
  runApp(const WidgetbookApp());
}
```
```

### Widgetbook in a Monorepo

Source: https://docs.widgetbook.io/essentials/monorepo

```mdx
# Widgetbook in a Monorepo

Widgetbook can be used in a monorepo setup. This guide will show you how to set up Widgetbook in a monorepo.

## Guide

### Single Widgetbook

The single Widgetbook approach is the simplest way to set up Widgetbook in a monorepo. You can create a single Widgetbook app that catalogs all the components from all the packages in the monorepo.

```
monorepo/
├── my_app/
├── packages/
│   └── my_design_system/
└── widgetbook/
```

```yaml title="widgetbook/pubspec.yaml"
name: widgetbook_workspace

dependencies:
  widgetbook_annotation: ^{{ versions.annotation }}
  widgetbook: ^{{ versions.widgetbook }}
  my_design_system:
    path: ../packages/my_design_system
  my_app:
    path: ../my_app

dev_dependencies:
  build_runner:
  widgetbook_generator: ^{{ versions.generator }}
```

#### Melos

If you run into dependency issues when using [Melos](https://melos.invertase.dev/) you might need to change how you bootstrap the dependencies.

```yaml title="widgetbook/pubspec.yaml"
name: widgetbook_workspace

dependencies:
  widgetbook_annotation: ^{{ versions.annotation }}
  widgetbook: ^{{ versions.widgetbook }}
  my_design_system: ^1.0.0
  my_app: ^1.0.0

dev_dependencies:
  build_runner:
  widgetbook_generator: ^{{ versions.generator }}
```

```yaml title="melos.yaml"
name: my_project

packages:
  - apps/**
  - packages/**
  - widgetbook/ // [!code ++]
```

Then run `melos bootstrap` to configure the dependencies.

### Per-package Widgetbook

The per-package Widgetbook approach is more complex but allows for more flexibility. You can create multiple Widgetbook apps, each cataloging the components from a single package in the monorepo.

```
monorepo/
├── my_app/
│   └── widgetbook/
└── packages/
    └── my_design_system/
        └── widgetbook/
```

```yaml title="my_app/widgetbook/pubspec.yaml"
name: my_app_widgetbook_workspace

dependencies:
  widgetbook_annotation: ^{{ versions.annotation }}
  widgetbook: ^{{ versions.widgetbook }}
  my_app:
    path: ../

dev_dependencies:
  build_runner:
  widgetbook_generator: ^{{ versions.generator }}
```

```yaml title="packages/my_design_system/widgetbook/pubspec.yaml"
name: my_design_system_widgetbook_workspace

dependencies:
  widgetbook_annotation: ^{{ versions.annotation }}
  widgetbook: ^{{ versions.widgetbook }}
  my_design_system:
    path: ../

dev_dependencies:
  build_runner:
  widgetbook_generator: ^{{ versions.generator }}
```
```

### What is a Design System?

Source: https://docs.widgetbook.io/glossary/design-system

```mdx
# What is a Design System?

A design system is a comprehensive set of guidelines, principles, components, and tools that guide the design and development of digital products. It acts as a single source of truth, ensuring consistency across different interfaces and platforms while fostering collaboration between designers and developers. Typically, a design system includes:

- **UI Components**: Pre-built buttons, icons, typography, color palettes, etc., that can be reused across projects.
- **Style Guide**: Rules governing visual elements, such as spacing, layout, and color usage.

In essence, a design system standardizes the building blocks of a product's UI, streamlining the process of designing and developing user interfaces.

### Why is a Design System Useful for Designers and Developers?

A design system offers several key benefits for both designers and developers:

1. **Consistency**: By establishing a single source of truth, a design system ensures that UI components and interactions remain consistent across different products, pages, or platforms. This helps maintain a cohesive user experience.

2. **Efficiency**: Designers no longer need to create components from scratch each time, and developers can avoid redundant code by reusing pre-built components. This speeds up both the design and development processes.

3. **Collaboration**: A well-documented design system bridges the gap between designers and developers. When both teams share the same reference point, it becomes easier to understand how to implement features correctly.

4. **Scalability**: As products grow and evolve, a design system allows for the rapid scaling of designs and features while maintaining coherence. New components or pages can be built without constantly reinventing the wheel.

5. **Quality Control**: With predefined rules and standards, design systems ensure that visual and functional quality remains high, reducing the risk of errors or inconsistencies.

## Why is it Difficult for Flutter Developers to Build Their Own Custom Design System?

While Flutter is known for its flexibility and the ability to build beautiful UIs, creating a custom design system in Flutter poses unique challenges, such as:

1. **Complexity of Widgets**: Flutter's UI is entirely composed of widgets, which are flexible but can be difficult to manage when creating a custom design system. Managing the relationships between different widgets and ensuring they work seamlessly together can become a complex task, especially when dealing with themes, styles, and custom animations.

2. **Lack of Native Design System Tools**: Unlike web development frameworks such as React, which have mature design system tools (like Storybook or Material UI), Flutter doesn't have an out-of-the-box tool specifically designed for creating or managing a design system. Flutter developers need to create, test, and maintain custom components, which can be time-consuming and require extra effort.

3. **Cross-Platform Complexity**: While Flutter’s cross-platform nature is one of its strengths, it can also be a hurdle when building a design system. Ensuring that custom components behave consistently across iOS, Android, and even web versions of a Flutter app adds another layer of complexity.

4. **Testing and Iteration**: Creating a custom design system involves regular testing and refinement. However, manually testing widgets and their variations across different devices can be tedious, slowing down development cycles.

## How Can Widgetbook Help?

Widgetbook is a an open source package that allows FLutter developers to create their custom design system. Inspired by Storybook.js, it is designed to simplify the creation, testing, and management of Flutter components. Widgetbook functions as a component library that makes it easier for developers and designers to visualize, experiment with, and organize their UI elements. Here’s how Widgetbook can assist Flutter developers in building and maintaining a custom design system:

1. **Component Visualization**: Widgetbook allows developers to see all their Flutter widgets in one place. This eliminates the need to manually build screens to test components, providing an interactive UI catalog where each widget and its variations can be previewed in real-time.

2. **Component Organization**: As a design system grows, keeping track of all components can be a challenge. Widgetbook offers a structured way to organize components, making it easier for developers and designers to find and reference them as needed.

3. **Theming and Variants**: Flutter developers often need to manage different themes or styles (e.g., light and dark modes) across an app. Widgetbook makes it easy to test components in multiple themes or states without writing additional code or building specific test cases.

4. **Cross-Platform Testing**: Since Flutter is used to build apps across multiple platforms, ensuring that components work on both iOS and Android is crucial. Widgetbook allows developers to quickly preview components in various screen sizes and configurations, making it easier to catch platform-specific issues early on.

5. **Collaboration**: Widgetbook Cloud promotes collaboration by creating a shared space where designers and developers can work together more effectively. Designers can visually check how components are implemented, and developers can get immediate feedback, speeding up the iteration process.

Here, you can read a [community article](https://medium.com/@mregnauld/how-to-implement-your-design-system-in-a-flutter-app-1-2-d2e21b5fcffd) on how to create your own design system in Flutter and how Widgetbook can help.


## Conclusion

A design system plays a critical role in modern software development by promoting consistency, efficiency, and collaboration between designers and developers. However, building a custom design system in Flutter can be challenging due to the complexity of managing widgets, cross-platform requirements, and a lack of specialized tools. Widgetbook addresses these pain points by offering an open source package for component management, testing, and collaboration, making it significantly easier for Flutter developers to create and maintain their own custom design system.
```

### What are Golden Tests / UI Regression Tests?

Source: https://docs.widgetbook.io/glossary/golden-tests

```mdx
# What are Golden Tests / UI Regression Tests?

UI regression tests, commonly referred to as golden tests, are a specialized form of testing aimed at ensuring that visual components of an application do not inadvertently change during code modifications. The "golden" refers to a reference image, often called a "golden snapshot" or "golden file," that captures the correct visual state of a UI at a certain point in time.

The basic premise is simple: you take a snapshot of a UI component (such as a widget in Flutter), and then any subsequent change to the UI is compared to this golden file. If the rendered UI differs from the golden image, the test fails, signaling a potential unintended visual change. This makes golden tests an essential part of maintaining UI integrity as applications scale and undergo continuous updates.

### Why Are Golden Tests Useful?

Golden tests are valuable for a number of reasons:

1. **Detecting Visual Regressions Early**: By comparing UI snapshots after each change, golden tests catch unintentional changes in the visual output early in the development process. This is especially useful in scenarios where UI changes might go unnoticed during regular testing.
   
2. **Automated Visual Validation**: Instead of relying on manual visual inspection, golden tests automate the process of validating the UI. This saves time, minimizes human error, and ensures consistent results.

3. **Maintaining Design Consistency**: For applications that need to follow strict design guidelines or branding requirements, golden tests help ensure that any changes don't compromise these standards. 

4. **Improved Collaboration**: Golden tests can serve as a shared visual standard across teams. Designers, developers, and QA teams can collaborate more effectively by using golden snapshots to track intended vs. unintended UI changes.

### Issues with Golden Tests for Flutter Developers

While golden tests provide significant advantages, they also come with certain challenges—particularly for Flutter developers.

1. **Platform-Specific Rendering**: Flutter is a cross-platform framework, meaning the same codebase is designed to run on multiple platforms (iOS, Android, web, desktop). Golden tests can become problematic when UI components render slightly differently on different platforms, leading to false positives where the tests fail even though the visual changes are minor or expected.

2. **Environmental Variability**: The appearance of UI elements can depend on the environment in which the tests are run, such as the screen resolution, text scaling, or device-specific configurations. Flutter developers often encounter issues where the golden files captured in one environment do not match those in another, even if the changes are not functionally significant.

3. **Maintenance Overhead**: As an application grows, so does the number of golden tests. Maintaining and updating golden snapshots can become cumbersome, particularly when intentional UI changes are made. Developers have to manually verify that each visual change is expected and update the golden files accordingly, which can slow down the development process.

4. **Dynamic Content**: UI elements that involve dynamic content (such as API-driven images, data, or animations) make golden tests harder to implement. Flutter apps, often built with dynamic, real-time content, might trigger false positives in golden tests due to small, yet legitimate, differences in the rendering.

5. **Writing Golden Tests**: To benefit from golden tests, developers need to write them. Especially for bigger applications where golden tests are most useful, this is time-consuming.

### How Widgetbook Cloud Can Help

To alleviate the challenges associated with golden tests for Flutter developers, **Widgetbook Cloud** offers a solution that enhances the workflow of testing, reviewing, and maintaining UI components.

1. **Zero-Configuration Golden Testing**: Widgetbook users don't need to write golden tests. Every widget that is cataloged in Widgetbook will automatically be golden tested in every PR.

1. **Cloud-Based Testing**: Widgetbook Cloud allows Flutter teams to run golden tests in a consistent, controlled environment. This eliminates platform-specific rendering discrepancies and environmental variability, as the tests are executed in a standard cloud environment where factors like screen resolution and device configurations are uniform.

2. **Collaborative Review Process**: One of Widgetbook Cloud's standout features is its collaborative approach to reviewing UI changes. Developers, designers, and product managers can collectively inspect and approve visual changes in real time. By centralizing UI reviews in a shared platform, the burden of verifying and maintaining golden tests is distributed across the team, improving efficiency and accuracy.

3. **Version Control for UI Components**: With Widgetbook Cloud, each UI component is versioned, which allows teams to track visual changes over time. This can reduce the maintenance overhead of golden files because changes are automatically documented and traceable. If a component’s appearance changes intentionally, the golden snapshot can be easily updated in a controlled manner.

4. **Simplified Maintenance**: Instead of manually updating and managing golden files, Widgetbook Cloud streamlines the process by allowing visual changes to be compared, reviewed, and approved in one place. This ensures that only the intended UI changes are accepted, reducing the risk of unintentional regressions while keeping maintenance efforts low.

### Conclusion

Golden tests are an essential part of maintaining UI consistency and detecting regressions in modern application development. However, they can be challenging to implement and maintain for Flutter developers due to platform variability, environmental factors, and dynamic content. Widgetbook Cloud automatically creates golden tests for Flutter teams and provides a comprehensive solution by offering a standardized, cloud-based platform for running and reviewing golden tests, improving collaboration, and simplifying the maintenance of golden snapshots. This ensures that Flutter teams can deliver visually consistent, high-quality user experiences with greater efficiency and confidence.
```

### Boolean Knob

Source: https://docs.widgetbook.io/knobs/boolean

```mdx
# Boolean Knob

The boolean knob renders a toggle switch in the Widgetbook UI where you can dynamically toggle a bool value for a widget property. 

## Variants

The boolean knob has two variants:
- `context.knobs.boolean()`: This variant allows you to toggle a bool value. It does not accept `null` values.
- `context.knobs.booleanOrNull()`: This variant allows you to toggle a bool value or `null`. It is useful when the property can be optional.

## `context.knobs.boolean()`
### Properties

Besides the knob's [base properties](/knobs/overview#properties), the boolean knob does not feature any additional properties.

### Example

<iframe 
  src="https://preview.widgetbook.io/#/?path=knobpreview/bool-knob&panels=knobs" 
  width="100%"
  height="240px"
/>

### Usage

To use a boolean knob, call the `context.knobs.boolean()` method.

```dart title="Example: Boolean Knob"
import 'package:flutter/material.dart';
import 'package:widgetbook/widgetbook.dart';
import 'package:widgetbook_annotation/widgetbook_annotation.dart';

@UseCase(type: Checkbox, name: 'Duo state')
Widget buildCheckboxUseCase(BuildContext context) {
  return Checkbox(
    value: context.knobs.boolean(label: 'value'), // [!code highlight]
    onChanged: (value) {},
  );
}
```

## `context.knobs.booleanOrNull()` 

### Properties

Besides the knob's [base properties](/knobs/overview#properties), the nullable boolean knob does not feature any additional properties.

### Example

<iframe 
  src="https://preview.widgetbook.io/#/?path=knobpreview/bool-nullable-knob&panels=knobs" 
  width="100%"
  height="240px"
/>

### Usage

To use the nullable boolean knob, call the `context.knobs.booleanOrNull()` method.

```dart title="Example: Nullable Boolean Knob"
import 'package:flutter/material.dart';
import 'package:widgetbook/widgetbook.dart';
import 'package:widgetbook_annotation/widgetbook_annotation.dart';

@UseCase(type: Checkbox, name: 'Tristate')
Widget buildCheckboxUseCase(BuildContext context) {
  return Checkbox(
    value: context.knobs.booleanOrNull(label: 'value'), // [!code highlight]
    tristate: true,
    onChanged: (value) {},
  );
}
```

## Multi-snapshot Support

Multi-snapshot support allows you to generate multiple screenshots of a single use case with varying values using [KnobsConfigs](/cloud/snapshots/multi-snapshot#multi-snapshot-for-knobs) and [AddonsConfigs](/cloud/snapshots/multi-snapshot#multi-snapshot-for-addons).

### Regular Boolean Knob

```dart title="Example: BooleanKnobConfig"
import 'package:flutter/material.dart';
import 'package:widgetbook/widgetbook.dart';
import 'package:widgetbook_annotation/widgetbook_annotation.dart';

@UseCase(
  type: Checkbox,
  name: 'Duo state',
  cloudKnobsConfigs: { // [!code highlight]
    'Checked': [BooleanKnobConfig('value', true)], // [!code highlight]
    'Unchecked': [BooleanKnobConfig('value', false)], // [!code highlight]
  }, // [!code highlight]
)
Widget buildCheckboxUseCase(BuildContext context) {
  return Checkbox(
    value: context.knobs.boolean(label: 'value'),
    onChanged: (value) {},
  );
}
```

### Nullable Bool Knob

```dart title="Example: NullKnobConfig"
import 'package:flutter/material.dart';
import 'package:widgetbook/widgetbook.dart';
import 'package:widgetbook_annotation/widgetbook_annotation.dart';

@UseCase(
  type: Checkbox,
  name: 'Tristate',
  cloudKnobsConfigs: { // [!code highlight]
    'Mixed': [NullKnobConfig('value')], // [!code highlight]
    'Checked': [BooleanKnobConfig('value', true)], // [!code highlight]
    'Unchecked': [BooleanKnobConfig('value', false)], // [!code highlight]
  }, // [!code highlight]
)
Widget buildCheckboxUseCase(BuildContext context) {
  return Checkbox(
    value: context.knobs.booleanOrNull(label: 'value'),
    onChanged: (value) {},
  );
}
```
```

### Color Knob

Source: https://docs.widgetbook.io/knobs/color

```mdx
# Color Knob

The color knob renders a combination of a color preview and input fields in the Widgetbook UI where you can dynamically enter a color value for a widget property. 

## Variants

The String knob has two variants:
- `context.knobs.color()`: This variant allows you to enter a color value as a string and color space combination. It does not accept `null` values.
- `context.knobs.colorOrNull()`: This variant allows you to enter a color value as a string and color space combination or set the value to `null`. It is useful when the property can be optional.

## Properties

In addition to the knob's [base properties](/knobs/overview#properties), the String knob includes the following:

<Accordion title="context.knobs.color()" defaultOpen>
  <Property name="initialColorSpace" type="ColorSpace" optional>
    The initial color space to use when displaying the color. Defaults to `ColorSpace.hex`. 
  </Property>
</Accordion>

## `context.knobs.color()`

### Example

<iframe 
  src="https://preview.widgetbook.io/#/?path=knobpreview/color-knob&panels=knobs" 
  width="100%"
  height="240px"
/>

### Usage

To use a color knob, call the `context.knobs.color()` method.

```dart title="Example: Color Knob"
@UseCase(type: MyWidget, name: 'Default')
Widget buildUseCase(BuildContext context) {
  return MyWidget(
    color: context.knobs.color(label: 'color', initialValue: Colors.red), // [!code highlight]
  );
}
```

## `context.knobs.colorOrNull()` 

### Example

<iframe 
  src="https://preview.widgetbook.io/#/?path=knobpreview/color-nullable-knob&panels=knobs" 
  width="100%"
  height="240px"
/>

### Usage

To use the nullable color knob, call the `context.knobs.colorOrNull()` method.

```dart title="Example: Nullable Color Knob"
@UseCase(type: MyWidget, name: 'Default')
Widget buildUseCase(BuildContext context) {
  return MyWidget(
    color: context.knobs.colorOrNull(label: 'color') // [!code highlight]
  );
}
```

## Multi-snapshot Support

Multi-snapshot support allows you to generate multiple screenshots of a single use case with varying values using [KnobsConfigs](/cloud/snapshots/multi-snapshot#multi-snapshot-for-knobs) and [AddonsConfigs](/cloud/snapshots/multi-snapshot#multi-snapshot-for-addons).

### Regular Color Knob

```dart title="Example: ColorKnobConfig"
import 'package:flutter/material.dart';
import 'package:widgetbook/widgetbook.dart';
import 'package:widgetbook_annotation/widgetbook_annotation.dart';

@UseCase(
  type: MyWidget,
  name: 'Default',
  cloudKnobsConfigs: { // [!code highlight]
    'Red': [ColorKnobConfig('color', 'FFF44336')], // [!code highlight]
  }, // [!code highlight]
)
Widget buildUseCase(BuildContext context) {
  return MyWidget(
    color: context.knobs.color(label: 'color', initialValue: Colors.red),
  );
}
```

### Nullable Color Knob

```dart title="Example: NullKnobConfig"
import 'package:flutter/material.dart';
import 'package:widgetbook/widgetbook.dart';
import 'package:widgetbook_annotation/widgetbook_annotation.dart';

@UseCase(
  type: MyWidget,
  name: 'Default',
  cloudKnobsConfigs: { // [!code highlight]
    'No color': [NullKnobConfig('color')], // [!code highlight]
    'Red': [ColorKnobConfig('color', 'FFF44336')], // [!code highlight]
  }, // [!code highlight]
)
Widget buildUseCase(BuildContext context) {
  return MyWidget(
    color: context.knobs.colorOrNull(label: 'color'),
  );
}
```
```

### Custom Knob

Source: https://docs.widgetbook.io/knobs/custom-knob

```mdx
# Custom Knob

If the built-in knobs do not meet your needs, you can create your own custom Knobs. This
allows you to extend the functionality of Widgetbook and tailor it to your specific requirements.

## Guide

In this guide, we will be creating a "range knob" that allows you to select a range of values using a `RangeSlider`.

### 0. Base structure

Start by creating a new file in your widgetbook project (e.g. `range_knob.dart`).

```dart title=widgetbook/lib/knobs/range_knob.dart
import 'package:flutter/material.dart';
import 'package:widgetbook/widgetbook.dart';

class RangeKnob extends Knob<RangeValues> {
  RangeKnob({
    required super.label,
    required super.initialValue,
  });

  @override
  List<Field> get fields {
    // TODO
  }

  @override
  RangeValues valueFromQueryGroup(Map<String, String> group) {
    // TODO
  }
}
```

Now let's start by implementing each of the class members step by step.

### 1. `fields` getter

This is a list of [fields](/essentials/fields) that describe how the knob will be represented in both:

* The Widgetbook UI – the "Knobs" panel.
* The URL query parameters.

In this case we need **two fields** to represent the `RangeValues`:

1. `DoubleInputField` - to pick a double value for the start of the range.
2. `DoubleInputField` - to pick a double value for the end of the range.

```dart
@override
List<Field> get fields => [
  DoubleInputField(
    name: '$label.min',
    initialValue: initialValue.start,
  ),
  DoubleInputField(
    name: '$label.max',
    initialValue: initialValue.end,
  ),
];
```

### 2. `valueFromQueryGroup` method

This method is responsible for parsing the query parameters back to the knob value (i.e. `RangeValues`). This can be done by using the `valueOf` helper method to extract the values from the query group by giving it **the field's name**.

```dart
@override
RangeValues valueFromQueryGroup(Map<String, String> group) {
  final start = valueOf<double>('$label.min', group)!;
  final end = valueOf<double>('$label.max', group)!;

  return RangeValues(start, end);
}
```

### 3. Registering the Knob

To use the custom knob, we need to create an extension on `KnobsBuilder`. This extension includes a `range` method that adds our `RangeKnob` to the builder.

```dart
extension RangeKnobBuilder on KnobsBuilder {
  RangeValues range({
    required String label,
    RangeValues initialValue = const RangeValues(0, 10),
  }) =>
      onKnobAdded(
        RangeKnob(
          label: label,
          initialValue: initialValue,
        ),
      )!;
}
```

### 4. Using the Knob

Now that we have implemented the `RangeKnob`, we can use it in our use cases. Here's an example of how to use it with a `RangeSlider` widget:

```dart title=widgetbook/lib/widgets/range_slider_use_case.dart
import 'package:flutter/material.dart';
import 'package:widgetbook/widgetbook.dart';
import 'package:widgetbook_annotation/widgetbook_annotation.dart';

import '../knobs/range_knob.dart';

@UseCase(name: 'CustomRangeSlider', type: RangeSlider)
Widget buildRangeSliderUseCase(BuildContext context) {
  return RangeSlider(
    values: context.knobs.range(label: 'Range'),
    max: 10,
    min: 0,
    onChanged: (_) {},
  );
}
```

## Multi-snapshot Support

Custom knobs can be used with [Multi Snapshot Reviews](/cloud/snapshots/multi-snapshot). The configuration varies based on the number of fields in your custom knob.

### Single-field Knobs

If your custom knob is a single-field knob, you can use  [`KnobConfig`](https://pub.dev/documentation/widgetbook_annotation/latest/widgetbook_annotation/KnobConfig-class.html) to configure it for multi-snapshot support. Here's an example:

```dart title=widgetbook/lib/widgets/custom_widget.dart
import 'package:widgetbook_annotation/widgetbook_annotation.dart';

@UseCase(
  name: 'Default',
  type: MyWidget,
  cloudKnobsConfigs: { // [!code highlight]
    'custom': [KnobConfig('foo', 'value')], // [!code highlight]
  }, // [!code highlight]
)
Widget buildUseCase(BuildContext context) {
  return MyWidget(
    foo: context.knobs.myKnob(label: 'foo'), // [!code highlight]
  );
}
```

### Multi-field Knobs

If your knobs has more than one field, _similar to the `RangeKnob` defined above_, you can use [`MultiFieldKnobConfig`](https://pub.dev/documentation/widgetbook_annotation/latest/widgetbook_annotation/MultiFieldKnobConfig-class.html) to configure it for multi-snapshot support. Here's how you can do it:

```dart title=widgetbook/lib/widgets/range_slider_use_case.dart
import 'package:flutter/material.dart';
import 'package:widgetbook/widgetbook.dart';
import 'package:widgetbook_annotation/widgetbook_annotation.dart';

import '../knobs/range_knob.dart';

@widgetbook.UseCase(
  name: 'CustomRangeSlider',
  type: RangeSlider,
  cloudKnobsConfigs: { // [!code highlight]
    'same range': [ // [!code highlight]
      MultiFieldKnobConfig({ // [!code highlight]
        'range.min': 5, // [!code highlight]
        'range.max': 5, // [!code highlight]
      }), // [!code highlight]
    ], // [!code highlight]
    'small range': [ // [!code highlight]
      MultiFieldKnobConfig({ // [!code highlight]
        'range.min': 4, // [!code highlight]
        'range.max': 6, // [!code highlight]
      }), // [!code highlight]
    ], // [!code highlight]
  }, // [!code highlight]
)
Widget buildRangeSliderUseCase(BuildContext context) {
  return RangeSlider(
    values: context.knobs.range(label: 'range'), // [!code highlight]
    max: 10,
    min: 0,
    onChanged: (_) {},
  );
}
```
```

### DateTime Knob

Source: https://docs.widgetbook.io/knobs/datetime

```mdx
# DateTime Knob

The DateTime knob renders a DateTime indicator field in the Widgetbook UI where you can enter a DateTime value via a date selector for a widget property. 

## Variants

The String knob has two variants:
- `context.knobs.dateTime()`: This variant allows you to enter a DateTime via a date and time picker. It does not accept `null` values.
- `context.knobs.dateTimeOrNull()`: This variant allows you to enter a DateTime via a date and time picker or `null`. It is useful when the property can be optional.

## Properties

In addition to the knob's [base properties](/knobs/overview#properties), the String knob includes the following:

<Accordion title="context.knobs.dateTime()" defaultOpen>
  <Property name="start" type="DateTime" required>
    The start time of the DateTime picker. This defines the earliest date that can be selected.
  </Property>
  <Property name="end" type="DateTime" required>
    The end time of the DateTime picker. This defines the latest date that can be selected.
  </Property>
</Accordion>

## `context.knobs.dateTime()`

### Example

<iframe 
  src="https://preview.widgetbook.io/#/?path=knobpreview/datetime-knob&panels=knobs" 
  width="100%"
  height="240px"
/>

### Usage

To use the DateTime knob, call the `context.knobs.dateTime()` method.

```dart title="Example: DateTime Knob"
@UseCase(type: MyWidget, name: 'Default')
Widget buildUseCase(BuildContext context) {
  return MyWidget(
    dateTime: context.knobs.dateTime( // [!code highlight]
      label: 'dateTime', // [!code highlight]
      initialValue: DateTime.now(), // [!code highlight]
      start: DateTime.now().subtract(const Duration(days: 30)), // [!code highlight]
      end: DateTime.now().add(const Duration(days: 30)), // [!code highlight]
    ), // [!code highlight]
  );
}
```

## `context.knobs.dateTimeOrNull()` 

### Example

<iframe 
  src="https://preview.widgetbook.io/#/?path=knobpreview/datetime-nullable-knob&panels=knobs" 
  width="100%"
  height="240px"
/>

### Usage

To use the nullable DateTime knob, call the `context.knobs.dateTimeOrNull()` method.

```dart title="Example: Nullable DateTime Knob"
@UseCase(type: MyWidget, name: 'Default')
Widget buildUseCase(BuildContext context) {
  return MyWidget(
    dateTime: context.knobs.dateTimeOrNull( // [!code highlight]
      label: 'dateTime', // [!code highlight]
      start: DateTime.now().subtract(const Duration(days: 30)), // [!code highlight]
      end: DateTime.now().add(const Duration(days: 30)), // [!code highlight]
    ), // [!code highlight]
  );
}
```

## Multi-snapshot Support

Multi-snapshot support allows you to generate multiple screenshots of a single use case with varying values using [KnobsConfigs](/cloud/snapshots/multi-snapshot#multi-snapshot-for-knobs) and [AddonsConfigs](/cloud/snapshots/multi-snapshot#multi-snapshot-for-addons).

### Regular DateTime Knob

```dart title="Example: DateTimeKnobConfig"
@UseCase(
  type: MyWidget,
  name: 'Default',
  cloudKnobsConfigs: { // [!code highlight]
    'dateTime': [DateTimeKnobConfig('dateTime', '2020-01-31 16:10')], // [!code highlight]
  }, // [!code highlight]
)
Widget buildUseCase(BuildContext context) {
  return MyWidget(
    dateTime: context.knobs.dateTime(
      label: 'dateTime',
      initialValue: DateTime.now(),
      start: DateTime.now().subtract(const Duration(days: 30)),
      end: DateTime.now().add(const Duration(days: 30)),
    ),
  );
}
```

### Nullable DateTime Knob

```dart title="Example: NullKnobConfig"
@UseCase(
  type: MyWidget,
  name: 'Default',
  cloudKnobsConfigs: { // [!code highlight]
    'Without date': [NullKnobConfig('dateTime')], // [!code highlight]
    'With date': [DateTimeKnobConfig('dateTime', '2020-01-31 16:10')], // [!code highlight]
  }, // [!code highlight]
)
Widget buildUseCase(BuildContext context) {
  return MyWidget(
    dateTime: context.knobs.dateTimeOrNull(
      label: 'dateTime',
      start: DateTime.now().subtract(const Duration(days: 30)),
      end: DateTime.now().add(const Duration(days: 30)),
    ),
  );
}
```
```

### Duration Knob

Source: https://docs.widgetbook.io/knobs/duration

```mdx
# Duration Knob

The Duration knob renders a set of text fields in the Widgetbook UI where you can dynamically enter a `Duration` value for a widget property. By default, it shows inputs for hours, minutes, and seconds.

## Variants

The Duration knob has two variants:
- `context.knobs.duration()`: Lets you enter a `Duration` value. It does not accept `null`.
- `context.knobs.durationOrNull()`: Lets you enter a `Duration` value or `null`. This is useful when the property is optional.

## Properties

Besides the knob's [base properties](/knobs/overview#properties), the Duration knob supports one additional property to control which time units are displayed:

| Property | Type | Default | Description |
|---|---|---|---|
| `units` | `Set<DurationUnit>` | `{DurationUnit.hours, DurationUnit.minutes, DurationUnit.seconds}` | The time units to show as separate inputs. |

`DurationUnit` has `days`, `hours`, `minutes`, `seconds`, `milliseconds`, and `microseconds`. Units render largest to smallest regardless of set order, and the largest absorbs any overflow so no value is hidden (e.g. with only `seconds`, `Duration(seconds: 90)` shows `90`).

## `context.knobs.duration()`

### Example

<iframe 
  src="https://preview.widgetbook.io/#/?path=knobpreview/duration-knob&panels=knobs" 
  width="100%"
  height="240px"
/>

### Usage

To use a Duration knob, call the `context.knobs.duration()` method.

```dart title="Example: Duration Knob"
@UseCase(type: MyWidget, name: 'Default')
Widget buildUseCase(BuildContext context) {
  return MyWidget(
    duration: context.knobs.duration(label: 'duration') // [!code highlight]
  );
}
```

To customize which time units are shown, pass a `units` set:

```dart title="Example: Duration Knob with custom units"
@UseCase(type: MyWidget, name: 'Default')
Widget buildUseCase(BuildContext context) {
  return MyWidget(
    duration: context.knobs.duration( // [!code highlight]
      label: 'duration', // [!code highlight]
      units: const { // [!code highlight]
        DurationUnit.days, // [!code highlight]
        DurationUnit.hours, // [!code highlight]
        DurationUnit.minutes, // [!code highlight]
        DurationUnit.seconds, // [!code highlight]
        DurationUnit.milliseconds, // [!code highlight]
        DurationUnit.microseconds, // [!code highlight]
      }, // [!code highlight]
    ), // [!code highlight]
  );
}
```

## `context.knobs.durationOrNull()`

### Example

<iframe 
  src="https://preview.widgetbook.io/#/?path=knobpreview/duration-nullable-knob&panels=knobs" 
  width="100%"
  height="240px"
/>

### Usage

To use the nullable Duration knob, call the `context.knobs.durationOrNull()` method.

```dart title="Example: Nullable Duration Knob"
@UseCase(type: MyWidget, name: 'Default')
Widget buildUseCase(BuildContext context) {
  return MyWidget(
    duration: context.knobs.durationOrNull(label: 'duration'), // [!code highlight]
  );
}
```

## Multi-snapshot Support

Multi-snapshot support allows you to generate multiple screenshots of a single use case with varying values using [KnobsConfigs](/cloud/snapshots/multi-snapshot#multi-snapshot-for-knobs) and [AddonsConfigs](/cloud/snapshots/multi-snapshot#multi-snapshot-for-addons).

### Regular Duration Knob

```dart title="Example: DurationKnobConfig"
@UseCase(
  type: MyWidget,
  name: 'Default',
  cloudKnobsConfigs: { // [!code highlight]
    'duration': [DurationKnobConfig('duration', 2000)], // [!code highlight]
  }, // [!code highlight]
)
Widget buildUseCase(BuildContext context) {
  return MyWidget(
    duration: context.knobs.duration(label: 'duration')
  );
}
```

### Nullable Duration Knob

```dart title="Example: NullKnobConfig"
@UseCase(
  type: MyWidget,
  name: 'Default',
  cloudKnobsConfigs: { // [!code highlight]
    'Without duration': [NullKnobConfig('duration')], // [!code highlight]
    'With duration': [DurationKnobConfig('duration', 2000)], // [!code highlight]
  }, // [!code highlight]
)
Widget buildUseCase(BuildContext context) {
  return MyWidget(
    duration: context.knobs.durationOrNull(label: 'duration')
  );
}
```
```

### Object Knob

Source: https://docs.widgetbook.io/knobs/object

```mdx
# Object Knob

The object knob allows you to select from a list of objects using different UI controls. This is useful for properties that require a dynamic input with different states or objects of a specific type, such as enums or custom classes.

## Variants and Usage

The object knob currently has the following variant:

- [Object Segmented Knob](#contextknobsobjectsegmented)

## Example: Object Segmented Knob

<iframe
  src="https://preview.widgetbook.io/#/?path=knobpreview/object-segmented-knob&panels=knobs"
  width="100%"
  height="240px"
/>

## `context.knobs.object.segmented()`

### Usage

To use the object segmented knob, call the `context.knobs.object.segmented()` method.

```dart title="Example: Object Segmented Knob"
@UseCase(type: MyWidget, name: 'Default')
Widget buildUseCase(BuildContext context) {
  return MyWidget(
    status: context.knobs.object.segmented(
      label: 'status',
      options: [
        OnlineStatusType.online,
        OnlineStatusType.offline,
        OnlineStatusType.busy,
      ],
      labelBuilder: (OnlineStatusType value) => value.name,
    ),
  );
}

enum OnlineStatusType { online, offline, busy }
```

## Properties

Besides the knob's [base properties](/knobs/overview#properties), the object segmented knob features:

- `options`: The list of objects to select from.
- `labelBuilder`: A function to convert an object to a display string.

## Multi-snapshot Support

Multi-snapshot support allows you to generate multiple screenshots of a single use case with varying values using [KnobsConfigs](/cloud/snapshots/multi-snapshot#multi-snapshot-for-knobs).

```dart title="Example: ObjectSegmentedKnobConfig"
@UseCase(
  type: MyWidget,
  name: 'Default',
  cloudKnobsConfigs: {
    'online': [ObjectSegmentedKnobConfig('status', 'online')],
    'offline': [ObjectSegmentedKnobConfig('status', 'offline')],
    'busy': [ObjectSegmentedKnobConfig('status', 'busy')],
  },
)
Widget buildUseCase(BuildContext context) {
  return MyWidget(
    status: context.knobs.object.segmented(
      label: 'status',
      options: [
        OnlineStatusType.online,
        OnlineStatusType.offline,
        OnlineStatusType.busy,
      ],
      labelBuilder: (OnlineStatusType value) => value.name,
    ),
  );
}
```
```

### Introduction to Knobs

Source: https://docs.widgetbook.io/knobs/overview

```mdx
# Introduction to Knobs

Knobs are dynamic tools in Widgetbook that allow you to modify the parameters passed to a use-case on the fly. 
They allow you to adapt and examine your widgets under various conditions and inputs, enhancing the overall understanding of a component's behavior.

<Image src="/assets/knobs/knobs.png" zoom />

## Usage

To incorporate knobs into your widgets, use the `context.knobs` function to access the knobs builder:

```dart title="Example: Knobs"
import 'package:flutter/material.dart';
import 'package:widgetbook/widgetbook.dart';
import 'package:widgetbook_annotation/widgetbook_annotation.dart' as widgetbook;

import '../main.dart';

@widgetbook.UseCase(name: 'with different title', type: Container)
Widget myWidget(BuildContext context) {
  return MyHomePage(
    title: context.knobs.string(
      label: 'Title Label',
      initialValue: 'HomePage',
    ),
  );
}
```

## Properties

Each Knob possesses a set of inherent properties to control its display in the Widgetbook's UI:

<Accordion title="Knob" defaultOpen>
  <Property name="label" type="String" required>
    A String that provides a title to the Knob. Each label must be unique for a WidgetbookUseCase.
  </Property>

  ---

  <Property name="initialValue" type="T" optional>
    The initial value of the knob when the WidgetbookUseCase is loaded. 
    The accepted type depends on the Knob. For instance, a [Non-nullable Integer Knob](/knobs/integer/overview) accepts `int` as a type. 
    The default value depends on the specifics of the knob but is `null` for nullable knobs.
  </Property>

  ---

  <Property name="description" type="String" optional>
    An optional String that explains the Knob's functionality. 
    It serves as a form of documentation within Widgetbook, assisting your team members in understanding how to manipulate the Knob.
  </Property>
</Accordion>

## Available Knobs

| Type                             | Regular Knob                                                                  | Nullable Knob                                                                             | Multi-snapshot Support                                                 |
| -------------------------------- | ----------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- | ---------------------------------------------------------------------- |
| [bool](/knobs/boolean)           | [boolean](/knobs/boolean#contextknobsboolean)                                 | [booleanOrNull](/knobs/boolean#contextknobsbooleanornull)                                 | [BooleanKnobConfig](/knobs/boolean#multi-snapshot-support)             |
| [int](/knobs/integer/overview)   | [int.input](/knobs/integer/input#contextknobsintinput)                        | [int.input](/knobs/integer/input#contextknobsintornullinput)                              | [IntKnobConfig](/knobs/integer/input#multi-snapshot-support)           |
| [int](/knobs/integer/overview)   | [int.slider](/knobs/integer/slider#contextknobsintslider)                     | [int.slider](/knobs/integer/slider#contextknobsintornullslider)                           | [IntKnobConfig](/knobs/integer/slider#multi-snapshot-support)          |
| [double](/knobs/double/overview) | [double.input](/knobs/double/input#contextknobsdoubleinput)                   | [doubleOrNull.input](/knobs/double/input#contextknobsdoubleornullinput)                   | [DoubleKnobConfig](/knobs/double/input#multi-snapshot-support)         |
| [double](/knobs/double/overview) | [double.slider](/knobs/double/slider#contextknobsdoubleslider)                | [doubleOrNull.slider](/knobs/double/slider#contextknobsdoubleornullslider)                | [DoubleKnobConfig](/knobs/double/slider#multi-snapshot-support)        |
| [String](/knobs/string)          | [string](/knobs/string#contextknobsstring)                                    | [stringOrNull](/knobs/string#contextknobsstringornull)                                    | [StringKnobConfig](/knobs/string#multi-snapshot-support)               |
| [Duration](/knobs/duration)      | [duration](/knobs/duration#contextknobsduration)                              | [durationOrNull](/knobs/duration#contextknobsdurationornull)                              | [DurationKnobConfig](/knobs/duration#multi-snapshot-support)           |
| [DateTime](/knobs/datetime)      | [dateTime](/knobs/datetime#contextknobsdatetime)                              | [dateTimeOrNull](/knobs/datetime#contextknobsdatetimeornull)                              | [DateTimeKnobConfig](/knobs/datetime#multi-snapshot-support)           |
| [Color](/knobs/color)            | [color](/knobs/color#contextknobscolor)                                       | [colorOrNull](/knobs/color#contextknobscolorornull)                                       | [ColorKnobConfig](/knobs/color#multi-snapshot-support)                 |
| [T](/knobs/iterable/overview)    | [iterable.segmented](/knobs/iterable/segmented#contextknobsiterablesegmented) | [iterableOrNull.segmented](/knobs/iterable/segmented#contextknobsiterableornullsegmented) | [IterableKnobConfig](/knobs/iterable/segmented#multi-snapshot-support) |
| [T](/knobs/object/overview)      | [object.dropdown](/knobs/object/dropdown#contextknobsobjectdropdown)          | [objectOrNull.dropdown](/knobs/object/dropdown#contextknobsobjectornulldropdown)          | [ObjectKnobConfig](/knobs/object/dropdown#multi-snapshot-support)      |
| [T](/knobs/object/overview)      | [object.segmented](/knobs/object/segmented#contextknobsobjectsegmented)       | [objectOrNull.segmented](/knobs/object/segmented#contextknobsobjectornullsegmented)       | [ObjectKnobConfig](/knobs/object/segmented#multi-snapshot-support)     |
```

### String Knob

Source: https://docs.widgetbook.io/knobs/string

```mdx
# String Knob

The String knob renders a text field in the Widgetbook UI where you can dynamically enter a string value for a widget property. 
This is particularly useful for properties that require a string input, such as a name or description.

## Variants

The String knob has two variants:
- `context.knobs.string()`: This variant allows you to enter a string value. It does not accept `null` values.
- `context.knobs.stringOrNull()`: This variant allows you to enter a string value or `null`. It is useful when the property can be optional.

## Properties

In addition to the knob's [base properties](/knobs/overview#properties), the String knob includes the following:

<Accordion title="context.knobs.string()" defaultOpen>
  <Property name="maxLines" type="int" optional>
    The number of lines the text field can display. Defaults to 1.
  </Property>
</Accordion>

## `context.knobs.string()`

### Example

<iframe 
  src="https://preview.widgetbook.io/#/?path=knobpreview/string-knob&panels=knobs" 
  width="100%"
  height="240px"
/>

### Usage

To use a String knob, call the `context.knobs.string()` method.

```dart title="Example: String Knob"
@UseCase(type: MyWidget, name: 'Default')
Widget buildUseCase(BuildContext context) {
  return MyWidget(
    name: context.knobs.string(label: 'Name', initialValue: 'John Doe'), // [!code highlight]
  );
}
```

## `context.knobs.stringOrNull()` 

### Example

<iframe 
  src="https://preview.widgetbook.io/#/?path=knobpreview/string-nullable-knob&panels=knobs" 
  width="100%"
  height="240px"
/>

### Usage

To use the nullable String knob, call the `context.knobs.stringOrNull()` method.

```dart title="Example: Nullable String Knob"
@UseCase(type: MyWidget, name: 'Default')
Widget buildUseCase(BuildContext context) {
  return MyWidget(
    name: context.knobs.stringOrNull(label: 'name', initialValue: 'John Doe'), // [!code highlight]
  );
}
```

## Multi-snapshot Support

Multi-snapshot support allows you to generate multiple screenshots of a single use case with varying values using [KnobsConfigs](/cloud/snapshots/multi-snapshot#multi-snapshot-for-knobs) and [AddonsConfigs](/cloud/snapshots/multi-snapshot#multi-snapshot-for-addons).

### Regular String Knob

```dart title="Example: StringKnobConfig"
import 'package:widgetbook/widgetbook.dart';

@UseCase(
  type: MyWidget,
  name: 'Default',
  cloudKnobsConfigs: { /// [!code highlight]
    'short name': [StringKnobConfig('name', 'John Doe')], // [!code highlight]
    'long name': [StringKnobConfig('name', 'Maximilian Mustermann')], // [!code highlight]
  }, // [!code highlight]
)
Widget buildUseCase(BuildContext context) {
  return MyWidget(
    name: context.knobs.string(label: 'name', initialValue: 'John Doe'),
  );
}
```

### Nullable String Knob

```dart title="Example: NullKnobConfig"
import 'package:widgetbook/widgetbook.dart';

@UseCase(
  type: MyWidget,
  name: 'Default',
  cloudKnobsConfigs: { /// [!code highlight]
    'no name': [NullKnobConfig('name')], // [!code highlight]
    'with name': [StringKnobConfig('name', 'John Doe')], // [!code highlight]
  }, // [!code highlight]
)
Widget buildUseCase(BuildContext context) {
  return MyWidget(
    name: context.knobs.stringOrNull(label: 'name'),
  );
}
```
```

### Annotations

Source: https://docs.widgetbook.io/use-cases/annotations

```mdx
# Annotations

When importing annotations from `widgetbook_annotation`, we recommend using the `widgetbook` alias to make the code more readable.

```dart
import 'package:widgetbook_annotation/widgetbook_annotation.dart' as widgetbook;
```

## @App

This annotation controls where the generated directories file should be located. It is used to specify the path to the generated files.
The following snippet generates a file at `lib/main.directories.g.dart` because the annotated class is located in `lib/main.dart`.

```dart
@widgetbook.App()
class WidgetbookApp extends StatelessWidget { ... }
```

## @UseCase

This annotation is used to add a **use-case** for a **component**. The following parameters are available:

| Parameter    | Required | Description                                                                                                                                                                                                                                                 |
| ------------ | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `name`       | ✅       | The name of the use-case.                                                                                                                                                                                                                                   |
| `type`       | ✅       | The `type` property is used by the generator to create the component entry within the navigation. You should specify the type of the cataloged component (e.g. `CoolButton`) instead of the one that might be returned by the builder method (e.g.`Center`) |
| `path`       | -        | A custom path for the use-case. Folders are delimited using slashes, path segments may be made into a category by enclosing it in square brackets. <br /> For example: `[Interactions]/buttons` will produce: `Interactions (category) -> buttons (folder)` |
| `designLink` | -        | A link to the figma design of the use-case to be used with [Widgetbook Cloud Reviews](/cloud/reviews#figma-x-flutter-comparison).                                                                                                                           |
| `exclude`    | -        | If set to `true`, the use-case will be excluded from the generated directories file. This can be useful for use-cases that are only used for certain environments (e.g. development or production).                                                         |

```dart
import 'package:flutter/material.dart';
import 'package:widgetbook_annotation/widgetbook_annotation.dart' as widgetbook;

// Import the widget from your app
import 'package:your_app/cool_button.dart';

@widgetbook.UseCase(name: 'Default', type: CoolButton)
Widget buildCoolButtonUseCase(BuildContext context) {
  return CoolButton();
}
```
```

### Generate use-cases with LLMs

Source: https://docs.widgetbook.io/use-cases/generate-with-llm

```mdx
# Generate use-cases with LLMs

<YouTube id="hrehRP54YuA" />

Writing use cases for components can be time-consuming — especially when dealing with many components in different states and configurations. 
Large Language Models (LLMs) can help automate this process by generating use cases based on a component's properties and behavior.

## Prompt

You can find the recommended prompt in the [`prompt.md`](https://github.com/widgetbook/widgetbook/tree/main/docs/assets/guides/use-case-llm-generation/prompt.md) file.

<Info>
We'd love your feedback on this prompt!  
Feel free to [start a discussion](https://github.com/widgetbook/widgetbook/discussions) or [contribute improvements](/contribution/ways-to-contribute).
</Info>

## Recommendations

For best results, consider the following tips:

- Use a recent LLM optimized for code generation.
- Work in IDEs like [Cursor](https://www.cursor.so/) or [Void](https://voideditor.com), or use extensions like GitHub Copilot to enable agentic LLM workflows. These tools provide better context awareness and integration with your codebase.
- Focus on one component/file at a time to give the LLM a clear scope.
- Always review and test the generated code before using it.
```

### Mocking

Source: https://docs.widgetbook.io/use-cases/mocking

```mdx
# Mocking

Adding a use-case for a widget that has external dependencies can be challenging.
In this guide, we'll explore how to mock these dependencies to make cataloging the widget easier.

## Video tutorial

<YouTube id="ytu7ADEsukE?start=1410&end=1730" />

## Example

In the following example, the `HomePage` widget depends on the `UserProvider` to display the user's name.

```dart
class UserProvider with ChangeNotifier {
  // For simplicity, we're hardcoding the user's name.
  // In a real-world scenario, this would be fetched from an API or a database.
  String user => "John Doe";
}
```

```dart
class UserTile extends StatelessWidget {
  const UserTile({super.key});

  @override
  Widget build(BuildContext context) {
    return Consumer<UserProvider>(
      builder: (context, provider, child) {
        return Text(provider.user);
      },
    );
  }
}
```

For this example, the Widget tree for your `HomePage` widget in your app might look like this:

```tree
App
└── UserProvider
    └── HomePage
        └── UserTile
            └── Consumer<UserProvider>
                └── Text
```

## Problem

If the `UserTile` is cataloged using the following code

```dart
Widget userTileUseCase(BuildContext context) {
  return UserTile();
}
```

Flutter throws an error indicating that a `UserProvider` is missing from the Widget tree, as the `Consumer` within the `UserTile` depends on the `UserProvider`.

```tree
WidgetbookApp
└── UserTile
    └── Consumer<UserProvider>         ❌ Error: UserProvider not found
        └── Text
```

To catalog the `UserTile` widget, you need to do **one of the following**:
1. Remove the dependency of the `UserTile` by using property extraction.
1. Provide the `UserProvider` to the Widget tree. 

## Approach I: Extraction

The simplest method to catalog `UserTile` is to extract the `UserProvider` dependency into a parameter.

```dart
class UserTile extends StatelessWidget {
  const UserTile({
    super.key,
    required this.user,
  });

  final String user;

  @override
  Widget build(BuildContext context) {
    return Text(user);
  }
}
```

```dart
@widgetbook.UseCase(name: 'Primary', type: UserTile)
Widget buildUserTile(BuildContext context) {
  return UserTile(
    user: 'John'
  );
}
```

Extracting the dependency into a parameter will also change your Widget tree structure of your app

```tree
App
└── UserProvider
    └── HomePage
        └── Consumer<UserProvider>
            └── UserTile
                └── Text
```

The Widget tree in Widgetbook will also change accordingly 

```tree
WidgetbookApp
└── UserTile
    └── Text
```

## Approach II: Mocking Libraries

Not in all cases, you can extract the dependency. In some case you need to mock the dependency, for example if you are cataloging a "screen" widget.

1. Add a mocking library to your `widgetbook/pubspec.yaml` file.

   <Info>
     It might feel weird seeing `mocktail` used as a `dependency` and not a
     `dev_dependency`, but the whole `widgetbook` app is a dev tool app.
   </Info>

   ```yaml
   dependencies:
     # ...
     mocktail: ^1.0.0
   ```

2. Mock `UserProvider` in the use-case builder function as follows

   ```dart
   class MockUserProvider extends Mock implements UserProvider {}

   @widgetbook.UseCase(name: 'Primary', type: UserTile)
   Widget buildUserTile(BuildContext context) {
    return ChangeNotifierProvider<UserProvider>(
      create: (_) {
        final provider = MockUserProvider();
        when(() => provider.user).thenReturn('Mocked User');

        return provider;
      },
      child: UserTile(),
    );
   }
   ```

In this approach, the Widget tree of your app stays the same

```tree
App
└── UserProvider
    └── HomePage
        └── UserTile
            └── Consumer<UserProvider>
                └── Text
```

But the Widget tree in Widgetbook will change according to your mocking

```tree
WidgetbookApp
└── MockUserProvider
    └── UserTile
        └── Consumer<UserProvider>
            └── Text
```
```

### What are use-cases?

Source: https://docs.widgetbook.io/use-cases/overview

```mdx
# What are use-cases?

A Use-case is a specific state or variant of a design component _(or a Flutter widget)_. It is a way to showcase a component in different scenarios.

## Use-cases for Components

Your design system will usually have multiple components, and each component will have multiple use-cases. For example, a button component might have use-cases for different states like `Primary`, `Secondary`, `Disabled` etc.

```dart title="components/lib/button.dart"
import 'package:flutter/material.dart';

enum ButtonState {
  primary,
  secondary,
  disabled,
}

class Button extends StatelessWidget {
  final String text;
  final ButtonState state;

  const Button({
    super.key,
    required this.text,
    required this.state,
  });

  @override
  Widget build(BuildContext context) {
    // Implementation of the button based on state
  }
}
```

```dart title="widgetbook/lib/button.dart"
import 'package:flutter/material.dart';
import 'package:widgetbook_annotation/widgetbook_annotation.dart';

import 'package:components/button.dart';

@UseCase(name: 'Primary', type: Button)
Widget primaryButton(BuildContext context) {
  return Button(
    text: 'Primary',
    state: ButtonState.primary,
  );
}

@UseCase(name: 'Secondary', type: Button)
Widget secondaryButton(BuildContext context) {
  return Button(
    text: 'Secondary',
    state: ButtonState.secondary,
  );
}

@UseCase(name: 'Disabled', type: Button)
Widget disabledButton(BuildContext context) {
  return Button(
    text: 'Disabled',
    state: ButtonState.disabled,
  );
}
```

<Info>
  In the previous code, instead of passing `text` as a constant value, you can
  use a [knob](/knobs/overview) to change the text value in the Widgetbook UI, and see
  how your button behaves with extra-long text, or with an empty text.
</Info>

## Use-cases for Screens

A screen is a composition of multiple components but as you move up the component hierarchy toward the screen level, you deal with more complexity. That's why it is **recommended** to catalog your screens in Widgetbook to be able to test them in isolation.

There are two common patterns for building screens:

1. **Pure Screens**: Screens that are fully presentational and don't depend on external data or services. You can create a use-case for them like any other component.
2. **Contained Screens**: Screens that depend on external data or services. Check out our [mocking guide](/use-cases/mocking) to know how to handle such screens.
```

### `cloud build push`

Source: https://docs.widgetbook.io/cli/commands/cloud-build-push

```mdx
# `cloud build push`

Pushes a [Widgetbook build](/cloud/builds/overview) to Widgetbook Cloud.

## Usage

```bash
widgetbook cloud build push --api-key <API_KEY>
```

## Arguments

The CLI accepts the following arguments.

| Argument       | Mandatory | Default\*                     | Description                                                      |
| -------------- | --------- | ----------------------------- | ---------------------------------------------------------------- |
| `--path`       | ➖        | `./`                          | The path to your project.                                        |
| `--api-key`    | ✅        | -                             | The project specific API key for Widgetbook Cloud.               |
| `--branch`     | ➖        | Current git branch            | The name of the branch for which the Widgetbook is uploaded.     |
| `--commit`     | ➖        | HEAD commit of current branch | The SHA hash of the commit for which the Widgetbook is uploaded. |
| `--repository` | ➖        | Repository top-level name     | The name of the repository for which the Widgetbook is uploaded. |
| `--actor`      | ➖        | Git config `user.name`        | The username of the actor which triggered the build.             |

_\* These defaults are used when the CLI is used locally.
If the CLI is used in a CI/CD environment, the defaults have different respective values._
```

### `coverage`

Source: https://docs.widgetbook.io/cli/commands/coverage

```mdx
# `coverage`

<Warning>
  This command is currently **experimental**, so any breaking changes can be
  introduced at any minor version.
</Warning>

Checks the percentage of package widgets that are covered by at least one use-case in Widgetbook.

## Usage

```bash
widgetbook coverage
```

## Arguments

The CLI accepts the following arguments.

| Argument         | Mandatory | Default\*      | Description                                       |
| ---------------- | --------- | -------------- | ------------------------------------------------- |
| `--package`      | ➖        | `./`           | Directory of the app or the design system package |
| `--widgetbook`   | ➖        | `./widgetbook` | Directory of the widgetbook app                   |
| `--min-coverage` | ➖        | `100`          | Minimum coverage percentage required              |

## Ignoring Widgets

Some widgets are not meant to be cataloged in Widgetbook (e.g. `App` widget). You can ignore these widgets by adding the following comment above the widget definition:

```dart
// widgetbook: ignore
class App extends StatelessWidget { ... }
```
```

### Billing

Source: https://docs.widgetbook.io/cloud/account/billing

```mdx
# Billing

Widgetbook Cloud subscriptions are billed monthly. Your billing cycle begins on the calendar day your subscription starts and renews on the same day each subsequent month.
For example, if you start on June 5, the first cycle runs from June 5 to July 5 and renews on July 5.

Any usage beyond your plan's included quota is billed as overage within the same billing period according to your plan's rates.

## Enterprise billing options

Annual and invoice-based billing are available only for enterprise customers. 
If you are interested in these options, please contact us at [contact@widgetbook.io](mailto:contact@widgetbook.io).

## How we count snapshots

Snapshot usage is measured at the workspace level. 
A workspace can contain multiple projects, and usage is the sum of all snapshots taken across all projects within the workspace. 
We do not show usage on a per-project basis.

## View your snapshot usage

You can view your current plan and usage at any time:

1. Open your workspace in Widgetbook Cloud.
2. Go to `Settings`.
3. Click `Billing`.

The Usage section shows your snapshot consumption and build count. 
The snapshot bar indicates how many snapshots you have used relative to your quota.

<Image src="/assets/cloud/account/billing.png" caption="Workspace Settings → Billing: plan details and snapshot usage" zoom />
```

### Privacy Policy

Source: https://docs.widgetbook.io/cloud/account/privacy-policy

```mdx
# Privacy Policy

Widgetbook GmbH
Ausblick 1
33100 Paderborn
Germany

VAT ID: DE352499885

Registered with: Local Court (Amtsgericht) Paderborn

Commercial Register Number: HRB 15814

Represented by: Jens Horstmann and Lucas Josefiak

Email: contact@widgetbook.io

---

## **2. Legal Basis for Processing**

The processing of personal data requires a legal basis, which we outline below.

Where the processing of personal data is based on the data subject’s consent, the legal basis is Article 6(1)(a) of the General Data Protection Regulation (GDPR).

Where the processing of personal data is necessary for the performance of a contract to which the data subject is a party, the legal basis is Article 6(1)(b) GDPR. This also applies to processing operations necessary to carry out pre-contractual measures.

Where the processing of personal data is necessary for compliance with a legal obligation to which our company is subject, the legal basis is Article 6(1)(c) GDPR.

Where processing is necessary for the purposes of a legitimate interest pursued by our company or a third party, and where such interests are not overridden by the interests or fundamental rights and freedoms of the data subject, the legal basis is Article 6(1)(f) GDPR. The legitimate interest of our company lies in the performance of our business operations and in the analysis, optimization, and security of our online offering.

---

## **3. Contractual Processing**

Personal data that you provide to us for contractual purposes (e.g. request for quotations) will be used exclusively internally, for the purpose of responding to your inquiries, processing your orders, or granting access to specific contractual information.

The data stored may include:

- Email address
- First and last name
- Profile picture
- Profession/Occupation/Job
- GitHub ID and username
- CI/CD provider

We will only use or disclose your data as described in this Privacy Policy.

---

## **4. Use of Data**

Widgetbook GmbH uses the collected data for the following purposes:

- To provide and maintain our service
- To notify you about changes to our service
- To enable you to participate in interactive features of our service, should you choose to do so
- To provide customer support
- To gather analysis or valuable information to improve our service
- To monitor the usage of our service
- To detect, prevent, and resolve technical issues
- To send you messages, special offers, and general information about goods, services, and events we offer that are similar to those you have already purchased or requested, unless you have opted out of receiving such information

---

## **5. Contact**

When contacting us via email or contact form, your information will be stored for the purpose of responding to your inquiry.

Personal data that you transmit to us through a contact request, an email, or direct business interaction may be processed and maintained by us using our Customer Relationship Management (CRM) system, namely HubSpot.

As a general rule, no data will be transferred to third parties unless permitted under applicable data protection law or if we are legally obligated to do so. You may withdraw your consent at any time with effect for the future. In case of withdrawal, your data will be deleted without delay unless statutory exceptions require continued processing. Otherwise, your data will be deleted once your inquiry has been addressed or the purpose of storage no longer applies and no other legal exceptions apply.

You may request information at any time regarding the personal data stored about you.

---

## **6. Cookies**

Our services use cookies. Cookies are small text files stored on your device that transmit certain information to the entity that sets the cookie. They serve to make our internet offering more user-friendly, efficient, and to facilitate navigation on our website.

This website uses the following types of cookies:

- Transient cookies (temporary use)
- Persistent cookies (time-limited use)

Transient cookies are automatically deleted when you close your browser. These include, in particular, session cookies. These store a randomly generated session ID that enables your browser’s requests to be assigned to the same session. This allows your device to be recognized upon return to our website.

Persistent cookies are automatically deleted after a predefined period, which can vary depending on the specific cookie.

You may, of course, use our website without accepting cookies. You can configure your browser settings as you wish, for example, to reject third-party cookies or all cookies, or to delete already stored cookies. Please note that if you choose not to accept cookies, our website may not function properly in certain areas.

To change your cookie preferences, please contact us at contact@widgetbook.io or use the button at the bottom of the website.

---

## **7. Registration**

Our website offers the possibility of user registration. The personal data entered during registration is transmitted to the controller and used exclusively for internal purposes. At the time of registration, the user’s IP address as well as the date and time of registration are stored to prevent misuse of our services.

Additionally, we store the email address, first and last name, and company name. This registration process is facilitated by the provider Clerk: https://clerk.com/legal/privacy

---

## **8. Our Presence on Social Media**

We maintain online presences on various social networks and platforms to communicate with active customers, prospects, and users, and to inform them about our services and company.

The processing of personal data of users on these platforms is based on our legitimate interest in communication and information exchange. Where user consent has been given on a specific platform, the processing is based on that consent.

When you visit one of our social media profiles, we and the operator of the platform are jointly responsible for the data processing activities triggered during your visit. You may exercise your rights (see section “Rights of the Data Subject”) both with us and with the platform operator.

Please note that despite the joint responsibility, we do not have full influence over the data processing carried out by the platform operator. We may need to forward your data subject request to the relevant provider. Our influence is limited to the provider’s corporate policies.

We have no influence over the data retention policies of platform providers. For more details, please refer to their respective privacy policies. Depending on the platform, user data may be processed outside the European Union. For U.S.-based companies, EU Standard Contractual Clauses have been agreed upon to ensure compliance with European data protection law.

In general, user data is processed by the platforms for market research and advertising purposes. Usage profiles may be created based on user behavior and interests. These profiles may be used to deliver targeted advertisements on and off the platforms. Cookies are typically stored on user devices to track usage behavior. These profiles may also include cross-device data, especially if the user is logged into the respective platform.

---

## **9. Disclosure of Data to Third Parties**

### **9.1 General and Contractual Purposes**

We disclose data to third parties where necessary for contract fulfillment, or if we are legally obliged or entitled to do so. All partners we work with state that they are GDPR-compliant.

### **9.2 Tools for the Economic Operation of the Website**

Within the scope of our legitimate interests in analysis, optimization, and the efficient operation of our online offering, we may use third-party service providers. This always requires that the third-party providers process users’ IP addresses, as they cannot display content otherwise. IP addresses are thus required for displaying such content.

Pseudonymized data may also be stored in cookies and may contain technical information similar to log files. The following is a list of our third-party providers. If we intend to use your data for any other purpose, we will inform you beforehand and only use your data with your explicit consent.

### **9.3 Third-Party Tools Mentioned in This Privacy Policy**

- **Neon:** For web applications. [**Privacy Policy‍**](https://neon.tech/privacy-policy)
- **Clerk:** For user management. [**Privacy Policy‍**](https://clerk.com/legal/privacy)
- **Vercel:** For development and hosting. [**Privacy Policy‍**](https://vercel.com/legal/privacy-policy)
- **AWS:** For cloud data storage. [**Privacy Policy‍**](https://aws.amazon.com/privacy/)
- **Google Cloud Platform:** For cloud data storage. [**Privacy Policy‍**](https://cloud.google.com/privacy)
- **Axiom:** For data analytics. [**Privacy Policy‍**](https://axiom.co/privacy)
- **Sentry:** For error monitoring. [**Privacy Policy‍**](https://sentry.io/privacy/)
- **Mixpanel:** For usage analytics. [**Privacy Policy‍**](https://mixpanel.com/legal/privacy-policy)
- **Mouseflow:** For user behavior analytics. [**Privacy Policy‍**](https://mouseflow.com/privacy)
- **HubSpot:** For CRM and marketing. [**Privacy Policy‍**](https://legal.hubspot.com/privacy-policy)
- **GitHub:** For code management. [**Privacy Policy‍**](https://docs.github.com/en/site-policy/privacy-policies/github-general-privacy-statement)
- **Stripe:** For payment processing. [**Privacy Policy**](https://stripe.com/privacy)

---

## **10. Rights of the Data Subject**

You have the right to obtain information about your personal data stored by us. Under statutory provisions, you also have the right to rectification of inaccurate data, restriction of processing, data portability, and erasure of your personal data. Please send such requests to contact@widgetbook.io with the subject “Data Protection”.

You also have the right to lodge a complaint with a supervisory authority if you believe the processing of your personal data infringes applicable data protection laws.

Where processing is based on your consent, you have the right to withdraw that consent at any time. The withdrawal does not affect the lawfulness of processing based on consent before its withdrawal.

You may also object at any time to the future processing of your personal data in accordance with legal requirements. In particular, you may object to processing for direct marketing purposes.

---

## **11. Retention Period of Personal Data**

Unless otherwise stated in the specific sections above, the following applies: We store personal data for the duration of the relevant statutory retention periods or as long as the purpose of the data collection exists.

After expiry of the retention period, data is routinely deleted unless it is still required for contract initiation or performance. Where data is not deleted because it is required for other and legally permissible purposes, its processing will be restricted as far as possible. In such cases, the data will be blocked and not processed for other purposes. This particularly applies to data required to be retained for commercial or tax law reasons.

---

## **12. Security Information**

The security of your data is important to us. However, please be aware that no method of transmission over the Internet or method of electronic storage is 100% secure.

We strive to use commercially acceptable means to protect your personal data but cannot guarantee absolute security.

We encrypt data both at rest and in transit. Transmission is protected using SSL/TLS encryption. Data at rest, including database backups, is also encrypted. Additionally, we implement access controls and Multi-Factor Authentication (MFA) to further secure access to data.
```

### Security Overview

Source: https://docs.widgetbook.io/cloud/account/security

```mdx
# Security Overview

At Widgetbook, security and privacy are at the core of our platform. We are committed to safeguarding your data while ensuring a seamless experience for developing and cataloging your Flutter components.

## Data Handling and Privacy

### Mocked Data Usage

We strongly encourage developers to use [mocked data](/use-cases/mocking) instead of real user data when using Widgetbook. This ensures that **no sensitive or personal information is stored or displayed** in Widgetbook or Widgetbook Cloud, including builds, reviews, and snapshots.

### Source Code Protection

Widgetbook Cloud **does not store source code**. Builds are compiled on your local machine or CI/CD and uploaded as a **binary artifact** to our servers, ensuring that the original source code remains under your full control. 

We store only minimal Git metadata necessary for collaboration and traceability:
- Git commit hash and message
- Git branch name
- Git pull request title and number

### Data Retention Policies

- Builds, Reviews, and Use-Cases are retained for **90 days** and automatically deleted afterward.
- Security logs, including audit and access logs, are retained for **up to one year** for internal security monitoring.

## GDPR Compliance

Widgetbook and Widgetbook Cloud are fully **GDPR-compliant**. We ensure:
- Transparent and lawful processing of personal data.
- Mechanisms for users to access, rectify, and delete their data.
- Adherence to data minimization principles by storing only necessary information.

For further details, refer to our [Privacy Policy](https://www.widgetbook.io/privacy-policy).

## Secure Cloud Infrastructure  

Widgetbook’s infrastructure is hosted within **AWS’s secure data centers**, ensuring high availability, robust security, and compliance with industry standards. 
AWS continuously assesses risks, implements stringent security measures, and undergoes regular audits to maintain the highest security standards. 
For more details, refer to [AWS Compliance Programs](https://aws.amazon.com/compliance/).

Our reliance on **AWS’s globally distributed infrastructure** allows us to deliver a **resilient, high-performance platform**, with built-in redundancy and failover mechanisms to safeguard your data. 
Additionally, AWS data centers are designed to meet **strict regulatory and security requirements**, ensuring the confidentiality, integrity, and availability of your data at all times.

## Security Measures

### Encryption

We use industry-standard encryption to protect data at all stages:

- **Data in Transit**: All communication between your devices and Widgetbook servers is secured using **TLS 1.2/1.3** encryption.
- **Data at Rest**: Sensitive data is encrypted using **AES-256** and other industry best practices provided by our cloud providers.

### Access Controls

To prevent unauthorized access, Widgetbook enforces strict access control mechanisms:

- **Authentication**: Secure authentication via OAuth and third-party identity providers.
- **Role-Based Access Control (RBAC)**: Permissions are assigned based on roles (e.g., administrator, member) to ensure least-privilege access.

## Third-Party Risk Management

Widgetbook relies on trusted cloud service providers:
- **AWS** for hosting and infrastructure security (see [AWS Security](https://aws.amazon.com/security/)).
- **Neon** for database management (see [Neon Security](https://neon.tech/docs/security/security-overview)).
- **Clerk** for authentication services (see [Clerk Security](https://clerk.com/docs/security/overview)).

We conduct **regular risk assessments** to ensure these providers maintain compliance with security best practices.

## User Responsibilities

While we ensure a **secure infrastructure**, we encourage users to follow best security practices:
- **Use strong, unique passwords** and protect access credentials.
- **Avoid uploading sensitive data** unless necessary.
- **Review permissions** regularly to maintain proper access control.

For any security concerns or responsible disclosures, please reach out to [contact@widgetbook.io](mailto:contact@widgetbook.io).
```

### Terms of Service

Source: https://docs.widgetbook.io/cloud/account/terms-of-service

```mdx
# Terms of Service 

## 1. DEFINITIONS

"Affiliate" means any entity that directly or indirectly controls, is controlled by, or is under common control with the subject entity. "Control," for purposes of this definition, means direct or indirect ownership or control of more than 50% of the voting interests of the subject entity

"Agreement" means this Main Services Agreement.

"Client" means in the case of an individual accepting this Agreement on his or her own behalf, such individual, or in the case of an individual accepting this Agreement on behalf of a company or other legal entity, the company or other legal entity for which such individual is accepting this Agreement, and Affiliates of that company or entity (for so long as they remain Affiliates) which have entered into Order Forms.

"Client Data" means electronic data and information submitted by or for Client to the Services, excluding Content and Non-Widgetbook Applications.

"Non-Widgetbook Application" means Web-based, mobile, offline or other software functionality that interoperates with a Service, that is provided by Client or a third party. Non-Widgetbook Applications, other than those obtained or provided by Client, will be identifiable as such.

"Order Form" means an ordering document or online order specifying the Services to be provided hereunder that is entered into between Client and Widgetbook or any of their Affiliates, including any addenda and supplements thereto. By entering into an Order Form hereunder, an Affiliate agrees to be bound by the terms of this Agreement as if it were an original party hereto.

"Services" means the products and services that are ordered by Client under an Order Form, or provided to Client free of charge (as applicable) or under a free trial, and made available online by Widgetbook.

"Statement of Work (SOW)" means a formal document that outlines the specific tasks, deliverables, and requirements of a project

"User" means, in the case of an individual accepting these terms on his or her own behalf, such individual, or, in the case of an individual accepting this Agreement on behalf of a company or other legal entity, an individual who is authorized by Client to use a Service, for whom Client has purchased a subscription. Users may include, for example, employees, consultants, contractors and agents of Client, and third parties with which Client transacts business.

"Widgetbook" means the company Widgetbook GmbH.

"Widgetbook Cloud" means the SaaS platform Widgetbook Cloud.

"Widgetbook OSS" means the Widgetbook open-source package.

## 2. WIDGETBOOK RESPONSIBILITIES

### 2.1 Provision of Purchased Services 
Widgetbook will (a) make the Services and Content available to Client pursuant to this Agreement, and the applicable Order Forms, and SOW's, (b) provide applicable support according to the Service Level Agreement (SLA) (c) use commercially reasonable efforts to make the Services available according to the SLA, except for: (i) planned downtime (of which Widgetbook shall give advance electronic notice), and (ii) any unavailability caused by circumstances beyond Widgetbook's reasonable control, including, for example, an act of God, act of government, flood, fire, earthquake, civil unrest, act of terror, strike or other labor problem (other than one involving Widgetbook employees), Internet service provider failure or delay, Non-Widgetbook Application, or denial of service attack, and (d) provide the Services in accordance with laws and government regulations applicable to Widgetbook's provision of its Services to its clients generally (i.e., without regard for Client's particular use of the Services), and subject to Client's and Users' use of the Services in accordance with this Agreement, and the applicable Order Form.

### 2.2 Protection of Client Data
Widgetbook will maintain appropriate administrative, physical, and technical safeguards for protection of the security, confidentiality and integrity of Client Data, as described in the Privacy Policy: https://www.widgetbook.io/privacy-policy, and the Documentation: https://docs.widgetbook.io/cloud/account/security. Those safeguards will include, but will not be limited to, measures designed to prevent unauthorized access to or disclosure of Client Data (other than by Client or Users).

### 2.3 Widgetbook Personnel
Widgetbook will be responsible for the performance of its personnel (including its employees and contractors) and their compliance with Widgetbook's obligations under this Agreement, except as otherwise specified in this Agreement.

## 3. USE OF SERVICE AND CONTENT

### 3.1 Subscriptions
Unless otherwise provided in the applicable Order Form or Documentation, Services and access to Content are purchased as subscriptions for the term stated in the applicable Order Form (b) any added subscriptions will terminate on the same date as the underlying subscriptions. Client agrees that its purchases are not contingent on the delivery of any future functionality or features, or dependent on any oral or written public comments made by Widgetbook regarding future functionality or features.

### 3.2 Usage Limits
Services and Content are subject to usage limits specified in Order Forms. If Client exceeds a contractual usage limit, Widgetbook charges Client for extra usage according to Order Forms.

### 3.3 Client Responsibilities
Client will (a) be responsible for Users' compliance with this Agreement, Documentation and Order Forms, (b) be responsible for the accuracy, quality and legality of Client Data, the means by which Client acquired Client Data, Client's use of Client Data with the Services, and the interoperation of any Non-Widgetbook Applications with which Client uses Services or Content, (c) use commercially reasonable efforts to prevent unauthorized access to or use of Services and Content, and notify Widgetbook promptly of any such unauthorized access or use, (d) ensure that all registration information User submits will be true, accurate, current, and complete (e) maintain the accuracy of such information and promptly update such registration information as necessary (f) ensure that User is not a minor in the jurisdiction in which they reside (g) not access the Service through automated or non-human means, whether through a bot, script or otherwise; (h) not use the Services for any illegal or unauthorized purpose, (i) ensure that their use of the Services will not violate any applicable law or regulation, or is incomplete, (j) in case of a required registration with our Services, agree to keep the password confidential and will be responsible for all use of the account and password, (k) comply with terms of service of any Non-Widgetbook Applications with which Client uses Services or Content. Any use of the Services in breach of the foregoing by Client or Users that in Widgetbook's judgment threatens the security, integrity or availability of Widgetbook's services, may result in Widgetbook's immediate suspension of the Services, however Widgetbook will use commercially reasonable efforts under the circumstances to provide Client with notice and an opportunity to remedy such violation or threat prior to any such suspension.

### 3.4 Usage Restrictions
Client will not (a) make any Service or Content available to anyone other than Client or Users, or use any Service or Content for the benefit of anyone other than Client or its Affiliates, unless expressly stated otherwise in an Order Form or the Documentation, (b) sell, resell, license, sublicense, distribute, rent or lease any Service or Content, or include any Service or Content in a service bureau or outsourcing offering, (c) use a Service or Non-Widgetbook Application to store or transmit infringing, libelous, or otherwise unlawful or tortious material, or to store or transmit material in violation of thirdparty privacy rights, (d) interfere with or disrupt the integrity or performance of any Service or third-party data contained therein, (e) attempt to gain unauthorized access to any Service or Content or its related systems or networks, (f) permit direct or indirect access to or use of any Services or Content in a way that circumvents a contractual usage limit, or use any Services to access, copy or use any of Widgetbook intellectual property except as permitted under this Agreement, an Order Form, or an SOW, (g) modify, copy, or create derivative works of a Service or any part, feature, function or user interface thereof, (h) copy Content except as permitted herein or in an Order Form or an SOW, (i) frame or mirror any part of any Service or Content, other than framing on Client's own intranets or otherwise for its own internal business purposes or as permitted in an SOW, (j) except to the extent permitted by applicable law, disassemble, reverse engineer, or decompile a Service or Content or access it to (1) build a competitive product or service, (2) build a product or service using similar ideas, features, functions or graphics of the Service, (3) copy any ideas, features, functions or graphics of the Service, or (4) determine whether the Services are within the scope of any patent.

### 3.5 Third-Party Website and Content
The Services may contain (or User may be sent via the Services) links to other websites ("Third-Party Websites") as well as articles, photographs, text, graphics, pictures, designs, music, sound, video, information, applications, software, and other content or items belonging to or originating from third parties ("Third-Party Content"). Such Third-Party Websites and Third-Party Content are not investigated, monitored, or checked for accuracy, appropriateness, or completeness by us, and Widgetbook is not responsible for any Third-Party Websites accessed through the Site or any Third-Party Content posted on, available through, or installed from the Site, including the content, accuracy, offensiveness, opinions, reliability, privacy practices, or other policies of or contained in the Third-Party Websites or the Third-Party Content. Inclusion of, linking to, or permitting the use or installation of any Third-Party Websites or any Third-Party Content does not imply approval or endorsement thereof by us. If User decide to leave the Site and access the Third-Party Websites or to use or install any ThirdParty Content, User does so at their own risk, and User should be aware these Terms and Conditions no longer govern. User should review the applicable terms and policies, including privacy and data gathering practices, of any website to which User navigates from the Service or relating to any applications User uses or install from the Site. Any purchases User makes through Third-Party Websites will be through other websites and from other companies, and Widgetbook takes no responsibility whatsoever in relation to such purchases which are exclusively between User and the applicable third party. User agrees and acknowledges that Widgetbook does not endorse the products or services offered on Third-Party Websites and User shall hold us harmless from any harm caused by Client purchase of such products or services. Additionally, User shall hold us harmless from any losses sustained by User or harm caused to User relating to or resulting in any way from any Third-Party Content or any contact with Third-Party Websites.

### 3.6 Modifications and Interruptions
Widgetbook ensures that planned changes to the Service do not materially affect Client. If changes materially affect Client, Widgetbook notifies Client at least 90 days in advance via electronic notice. If changes do not materially affect Client, Widgetbook reserves the right to change, modify, or remove the contents of the Service at any time or for any reason at our sole discretion without notice if the changes do not materially affect Client. However, Widgetbook has no obligation to update any information on our Service. Widgetbook also reserves the right to modify or discontinue all or part of the Service without notice at any time. Widgetbook will not be liable to Client or any third party for any modification, price change, suspension, or discontinuance of the Service. In case the changes materially affect Client, Widgetbook needs to inform Client about the changes at least 90 days in advance via electronic notice. If Client has no Service Level Agreement ("SLA") with Widgetbook, Widgetbook can not guarantee the Service will be available at all times. Widgetbook may experience hardware, software, or other problems or need to perform maintenance related to the Service, resulting in interruptions, delays, or errors. Widgetbook reserves the right to change, revise, update, suspend, discontinue, or otherwise modify the Service at any time or for any reason without notice to Client. Client agrees that Widgetbook has no liability whatsoever for any loss, damage, or inconvenience caused by Client's inability to access or use the Service during any downtime or discontinuance of the Service. Nothing in these Terms and Conditions will be construed to obligate Widgetbook to maintain and support the Service or to supply any corrections, updates, or releases in connection therewith.

## 4. TERM AND TERMINATION

### 4.1 Term
This Agreement will commence on the Effective Date mentioned in the Order Form and continues until all subscriptions hereunder have expired or have been terminated.

### 4.2 Term of Purchased Subscriptions
The term of each subscription shall be as specified in the applicable Order Form. Except as otherwise specified in an Order Form, subscriptions will automatically renew for additional one-year terms, unless either party gives the other written notice (email acceptable) at least 30 days before the end of the relevant subscription term. Except as expressly provided in the applicable Order Form, renewal of promotional or one-time priced subscriptions will be at Widgetbook's applicable list price in effect at the time of the applicable renewal.

### 4.3 Termination
A party may terminate this Agreement for cause (i) upon 30 days written notice to the other party of a material breach if such breach remains uncured at the expiration of such period, or (ii) if the other party becomes the subject of a petition in bankruptcy or any other proceeding relating to insolvency, receivership, liquidation or assignment for the benefit of creditors

### 4.4 Refund or Payment upon Termination
If this Agreement is terminated by Client in accordance with the "Termination" section above, Widgetbook will refund Client any prepaid fees covering the remainder of the term of all Order Forms after the effective date of termination. If this Agreement is terminated by Widgetbook in accordance with the "Termination" section above, Client will pay any unpaid fees covering the remainder of the term of all Order Forms to the extent permitted by applicable law. In no event will termination relieve Client of its obligation to pay any fees payable to Widgetbook for the period prior to the effective date of termination.

## 5. FEES AND PAYMENT

### 5.1 Fees
Fees will be invoiced in advance and otherwise in accordance with the relevant Order Form.

### 5.2 Invoicing
Widgetbook will submit invoices in accordance with the payment terms in the applicable Order Form. Payments are due within 10 (10) days of the invoice date and otherwise in accordance with the relevant Order Form.

### 5.3 Late Payments
Any payments not received by the due date will be subject to interest at a rate of 5% per month, or the maximum amount permitted by law, whichever is lower.

### 5.4 Taxes
Widgetbook's fees do not include any taxes, levies, duties or similar governmental assessments of any nature, including, for example, value-added, sales, use or withholding taxes, assessable by any jurisdiction whatsoever (collectively, "Taxes"). Client is responsible for paying all Taxes associated with its purchases hereunder. If Widgetbook has the legal obligation to pay or collect Taxes for which Client is responsible under this section, Widgetbook will invoice Client and Client will pay that amount unless Client provides Widgetbook with a valid tax exemption certificate authorized by the appropriate taxing authority. For clarity, Widgetbook is solely responsible for taxes assessable against it based on its income, property and employees.

### 5.5 Non-Enterprise Plans
5.1 – 5.4. are valid for Enterprise Plans only. Non-Enterprise Plans are paid via Stripe. All payment methods that are available in Stripe are possible. Client agrees to promptly update their account and payment information, including email address, payment method, and payment card expiration date, so that Widgetbook can complete the transactions and contact Client as needed. Sales tax will be added to the price of purchases as deemed required by Widgetbook. All payments shall be in Euros or USD. Client agrees to pay all charges or fees at the prices then in effect for your purchases, and authorizes Widgetbook to charge the chosen payment provider for any such amounts upon making the purchase. If the purchase is subject to recurring charges, then Client consents Widgetbook to charging the payment method on a recurring basis without requiring Client's prior approval for each recurring charge, until Client notifies Widgetbook of the cancellation.
Widgetbook reserves the right to correct any errors or mistakes in pricing, even if we have already requested or received payment. Widgetbook also reserves the right to refuse any order.

## 6. REFERENCES

### 6.1 Reference customers
Reference customers are invaluable for Widgetbook. Client permits Widgetbook to use Client's company name and logo on their website and marketing materials, and to be featured in case studies. Widgetbook handles Client's information with care and deeply appreciates the trust in its products and services. Client can revoke the consent for future references in writing at any time.

## 7. CONFIDENTIALITY

### 7.1 Definition of Confidential Information
"Confidential Information" means any non-public, proprietary, or confidential information disclosed by one Party ("Disclosing Party") to the other Party ("Receiving Party") in connection with this Agreement, whether orally, in writing, or through other means.

### 7.2 Obligations
The Receiving Party will (a) maintain the confidentiality of the Disclosing Party's Confidential Information; (b) not disclose or permit the disclosure of Confidential Information to any third party without the Disclosing Party's prior written consent; and (c) use the Confidential Information solely for the purpose of fulfilling its obligations under this Agreement.

### 7.3 Exclusions
Confidential Information does not include information that (a) is or becomes publicly known through no fault of the Receiving Party; (b) is lawfully received by the Receiving Party from a third party without restriction; (c) is independently developed by the Receiving Party without reference to the Disclosing Party's Confidential Information.

### 7.4 Compelled Disclosure
If the Receiving Party is required by law or court order to disclose Confidential Information, the Receiving Party will provide prompt written notice to the Disclosing Party and cooperate in seeking a protective order.

## 8. INTELLECTUAL PROPERTY

### 8.1 Ownership
Unless otherwise stated in a SOW, Widgetbook will retain all right, title, and interest in and to any pre-existing materials, tools, software, or intellectual property used in connection with the Services ("Widgetbook IP").

### 8.2 Deliverables
Upon payment in full, Client will have a non-exclusive, royalty-free license to use the deliverables created by Widgetbook under this Agreement solely for Client's internal business purposes.

### 8.3 Feedback
Any feedback or suggestions provided by Client to Widgetbook may be used by Widgetbook without restriction or compensation.

## 9. WARRANTIES AND DISCLAIMERS

### 9.1 Mutual Warranties
Each Party represents and warrants that (a) it has the authority to enter into this Agreement and perform its obligations; (b) its performance under this Agreement will comply with all applicable laws and regulations.

### 9.2 Widgetbook Warranties
Widgetbook warrants that the Services will be performed in a professional and workmanlike manner.

### 9.3 Disclaimer
EXCEPT FOR THE EXPRESS WARRANTIES PROVIDED IN THIS AGREEMENT, WIDGETBOOK DISCLAIMS ALL WARRANTIES, WHETHER EXPRESS, IMPLIED, STATUTORY, OR OTHERWISE, INCLUDING BUT NOT LIMITED TO IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT.

## 10. LIMITATION OF LIABILITY

### 10.1 Exclusion of Damages
IN NO EVENT WILL EITHER PARTY BE LIABLE FOR ANY INDIRECT, INCIDENTAL, CONSEQUENTIAL, SPECIAL, OR PUNITIVE DAMAGES ARISING OUT OF OR RELATED TO THIS AGREEMENT, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.

### 10.2 Liability Cap
EACH PARTY'S TOTAL LIABILITY UNDER THIS AGREEMENT WILL NOT EXCEED THE TOTAL FEES PAID OR PAYABLE TO WIDGETBOOK UNDER THE APPLICABLE ORDER FORM IN THE TWELVE (12) MONTHS PRECEDING THE EVENT GIVING RISE TO THE CLAIM.

## 11. GENERAL PROVISIONS

### 11.1 Independent Contractor
Widgetbook is an independent contractor, and nothing in this Agreement shall be construed to create a partnership, joint venture, or employment relationship between the Parties.

### 11.2 Governing Law
This Agreement shall be governed by and construed in accordance with the laws of Germany, without regard to conflicts of law principles.

### 11.3 Dispute Resolution
Any disputes arising under this Agreement will be resolved through good faith negotiations. If the Parties cannot resolve the dispute within thirty (30) days, either Party may submit the dispute to binding arbitration in Germany.

### 11.4 Entire Agreement
This Agreement, including any SOWs, constitutes the entire agreement between the Parties and supersedes all prior agreements, representations, and understandings.

### 11.5 Amendments
Any amendments to this Agreement must be in writing and signed by both Parties.

### 11.6 Notices
All notices under this Agreement must be in writing and signed by both Parties.

### 11.7 Severability
If any provision of this Agreement is found to be invalid or unenforceable, the remaining provisions will remain in full force and effect.

### 11.8 Waiver
No failure or delay in exercising any right under this Agreement shall constitute a waiver of that right.
```

### Access build from Git commit

Source: https://docs.widgetbook.io/cloud/builds/access-from-commit

```mdx
# Access build from Git commit

Since you manage your code with a Git provider, Widgetbook Cloud aims to seamlessly **integrate with your Git provider** to make Widgetbook builds accessible to your team.
Therefore, **Widgetbook Cloud links builds to commit statuses** of your Git provider.

As Git provider implementations differ, find descriptions for your provider below.

<Image
  src="/assets/cloud/builds/github-link.png"
  caption="Widgetbook builds are accessed via commit statuses"
  zoom
/>

<CardGroup>
  <Card title="GitHub" href="/cloud/guides/github/access-from-commit">
    Synchronize Widgetbook builds with GitHub's commit statuses.
  </Card>
  <Card title="GitLab" href="/cloud/guides/gitlab/access-from-commit">
    Synchronize Widgetbook builds with GitLab's commit statuses.
  </Card>
  <Card title="Azure" href="/cloud/guides/azure/access-from-commit">
    Synchronize Widgetbook builds with Azure's commit statuses.
  </Card>
  <Card title="Bitbucket" href="/cloud/guides/bitbucket/access-from-commit">
    Synchronize Widgetbook builds with Bitbucket's commit statuses.
  </Card>
</CardGroup>
```

### Overview

Source: https://docs.widgetbook.io/cloud/builds/overview

```mdx
# Overview

With _Widgetbook Builds_ developers can host a Widgetbook for each Git commit and make the specific Widgetbook easily accessible for the whole team on Widgetbook Cloud.
When setup with CI/CD, you consistantly upload builds for each commit, pull-request and branch and make those builds accessible from your favorite Git provider.

<Image src="/assets/cloud/projects/overview.png" caption="List of builds with their corresponding commit SHA and branch" zoom />

## Versioning Widgetbook

Making adjustments to your Flutter frontend code means creating new versions of:
- your components  
- your app  
- your Widgetbook  
These updates are made by submitting new commits.

Developers often need to share specific versions with their team — including developers, designers, QA testers, and product managers.

Widgetbook Cloud supports **versioning with builds** by:
- Uploading the Widgetbook build for every commit  
- Hosting builds for each branch

## Status

Widgetbook Cloud expects that builds are immutable, therefore only one build with a specific commit SHA can exist for each project.
In the lifecycle of a build, a build transitions through the following statuses:
- **Draft:** The upload of a new build has been requested and is in the process of uploading.
- **In progress:** The build uploaded and we're processing the snapshots.
- **Successful:** The build successfully uploaded and all snapshots are processed.
- **Failed:** The build failed to upload or process snapshots.
```

### Turbo Builds ⚡

Source: https://docs.widgetbook.io/cloud/builds/turbo

```mdx
# Turbo Builds ⚡

Turbo Builds is a feature that can **save you a lot of time** when pushing builds to Widgetbook Cloud. It works by creating a unique hash for each build depending on it's contents. So if you changed build-irrelevant files (e.g. test files, README, CI/CD configs, etc.) the build will be **instantly** available in Widgetbook Cloud without having to wait for the build to process.

## Caveats

Turbo Builds might misbehave in some cases like:

1. If you use external URLs (e.g. image sources) in your build, and the content of those URLs change but the URL remains the same.
1. You use any network requests that might change the use-case behavior.

## Disable Turbo Builds

If you would like to disable Turbo Builds, you can do so by specifying the `--no-turbo` flag when running the `widgetbook cloud build push` command:

```bash
widgetbook cloud build push --no-turbo --api-key <API_KEY>
```
```

### Upload builds

Source: https://docs.widgetbook.io/cloud/builds/upload

```mdx
# Upload builds

A build can be uploaded to Widgetbook Cloud using the Widgetbook CLI.
We recommend to setup a CI/CD workflow to automatically upload builds for each commit and branch.

## Tutorial video

<YouTube id="-7w1VqZD004" />

## Upload build using CI/CD

<Info>
Uploading builds using CI/CD is the recommended way to publish Widgetbook Cloud builds for each of your commits.
If you're trying Widgetbook Cloud for the first time, following the [manual upload instructions](/cloud/builds/upload#upload-build-manually) is helpful to understand the process.
</Info>

To follow the CI/CD setup guide select your Git provider below

<CardGroup>
    <Card title="GitHub" href="/cloud/guides/github/upload">
        Upload builds with GitHub action.
    </Card>
    <Card title="GitLab" href="/cloud/guides/gitlab/upload">
        Upload builds with GitLab pipelines.
    </Card>
    <Card title="Azure DevOps" href="/cloud/guides/azure/upload">
        Upload builds with Azure Pipelines.
    </Card>
    <Card title="Bitbucket" href="/cloud/guides/bitbucket/upload">
        Upload builds with Bitbucket Pipelines.
    </Card>
</CardGroup>

## Upload build manually

To create a Widgetbook Build, follow these steps inside your Widgetbook project:

1. Run `widgetbook_generator` to generate metadata about your use-cases and components

   ```bash
   dart run build_runner build -d
   ```

1. Build the Widgetbook for the web

   ```bash
   # Default target (i.e. `lib/main.dart`)
   flutter build web

   # Custom target
   flutter build web -t lib/main.widgetbook.dart
   ```

1. Install the [Widgetbook CLI](/cli)

   ```bash
   dart pub global activate widgetbook_cli
   ```

1. Get your **API key** from the Widgetbook Cloud's **project settings page**.

1. Push the build to Widgetbook Cloud

   ```bash
   widgetbook cloud build push --api-key PROJECT_API_KEY
   ```

<Info>
The `cloud push` command uses the following directories:

1. `build/web/` to create a `.zip` archive that will be uploaded to Widgetbook Cloud.
1. `.dart_tool/build/generated/[your_app_name]/` to send metadata, _about the generated use-cases_, that will be used for [Widgetbook Cloud Review](/cloud/reviews).

</Info>
```

### Setup with Demo Project

Source: https://docs.widgetbook.io/cloud/guides/demo-project

```mdx
# Setup with Demo Project

This guide will walk you through setting up a demo project to test Widgetbook Cloud. 
We'll use the [groceries-demo](https://github.com/widgetbook/groceries-demo) repository as our example project.

## Prerequisites

Before starting this guide, make sure you have:

- An account for GitHub, GitLab, or Bitbucket
- Access to [Widgetbook Cloud](https://app.widgetbook.io/)
- Basic knowledge of Git and your chosen Git provider

## Steps

<Steps>
  <Step title="Clone the Demo Repository">
    <Tabs
      groupId="git-provider"
      values={[
        { label: "GitHub", value: "github" },
        { label: "GitLab", value: "gitlab" },
        { label: "Bitbucket", value: "bitbucket" },
      ]}
    >
      <TabItem value="github">
        Since you're using GitHub, you can fork the repository directly:
        
        1. Navigate to the [groceries-demo repository](https://github.com/widgetbook/groceries-demo)
        1. Click the **Fork** button in the top-right corner
        1. Select your personal account as the destination
        1. Wait for the forking process to complete
        1. Clone your forked repository:
        
        ```bash
        git clone https://github.com/YOUR_USERNAME/groceries-demo.git
        cd groceries-demo
        ```
        
        <Info>
          Use your personal GitHub account for forking, as business accounts might lack permission to connect repositories to Widgetbook Cloud.
        </Info>
      </TabItem>
      
      <TabItem value="gitlab">
        Since you're using GitLab, you can import the repository directly:

        1. Go to your GitLab account and click **New project**
        2. Select **Import project**
        3. Choose **Repository by URL** as the import source
        4. Enter the repository URL: `https://github.com/widgetbook/groceries-demo`
        5. Set the project name to `groceries-demo`
        6. Choose the visibility level (public or private)
        7. Click **Create project**
        8. Wait for the import to complete
        9. Clone your imported repository:

        ```bash
        git clone https://gitlab.com/YOUR_USERNAME/groceries-demo.git
        cd groceries-demo
        ```

        <Info>
          Use your personal GitLab account for the repository import, as business accounts might lack permission to connect repositories to Widgetbook Cloud.
        </Info>
      </TabItem>
      
      <TabItem value="bitbucket">
        Since you're using Bitbucket, you can import the repository directly:

        1. Go to your Bitbucket account and click **Create repository**
        2. Click **Import repository**
        3. Enter the repository URL: `https://github.com/widgetbook/groceries-demo`
        4. Set the repository name to `groceries-demo`
        5. Choose the repository type (public or private)
        6. Click **Import repository**
        7. Wait for the import to complete
        8. Clone your imported repository:

        ```bash
        git clone https://bitbucket.org/YOUR_USERNAME/groceries-demo.git
        cd groceries-demo
        ```
      </TabItem>
    </Tabs>
  </Step>

  <Step title="Create a Widgetbook Cloud Workspace">
    1. Navigate to [Widgetbook Cloud](https://app.widgetbook.io/)
    2. Sign in with your account
    3. Click **Create Workspace** or use an existing workspace
    4. Enter a name for your workspace (e.g., "Demo Workspace")
    5. Click **Create** to finalize the workspace creation
  </Step>

  <Step title="Create a Widgetbook Cloud Project">
    <Tabs
      groupId="git-provider"
      values={[
        { label: "GitHub", value: "github" },
        { label: "GitLab", value: "gitlab" },
        { label: "Bitbucket", value: "bitbucket" },
      ]}
    >
      <TabItem value="github">
        1. In your Widgetbook Cloud workspace, click **Create Project**
        2. Select **GitHub** as your Git provider
        3. If you haven't connected your GitHub account yet, click **Connect GitHub** and follow the authorization process
        4. Search for your forked `groceries-demo` repository 
        5. Select the repository and click **Import Project**
        6. Wait for the project to be created and initialized
      </TabItem>
      
      <TabItem value="gitlab">
        1. In your Widgetbook Cloud workspace, click **Create Project**
        2. Select **GitLab** as your Git provider
        3. If you haven't connected your GitLab account yet, click **Connect GitLab** and follow the authorization process
        4. Search for your `groceries-demo` repository
        5. Select the repository and click **Import Project**
        6. Wait for the project to be created and initialized
      </TabItem>
      
      <TabItem value="bitbucket">
        1. In your Widgetbook Cloud workspace, click **Create Project**
        2. Select **Bitbucket** as your Git provider
        3. If you haven't connected your Bitbucket account yet, click **Connect Bitbucket** and follow the authorization process
        4. Search for your `groceries-demo` repository
        5. Select the repository and click **Import Project**
        6. Wait for the project to be created and initialized
      </TabItem>
    </Tabs>
  </Step>

  <Step title="Get the API Key">
    1. In your newly created Widgetbook Cloud project, navigate to **Settings**
    2. Find the **API Key** section
    3. Copy the API key 
    
    <Info>
      Keep this API key secure. You'll need it to configure your CI/CD pipeline to upload builds to Widgetbook Cloud.
    </Info>
  </Step>

  <Step title="Add the API Key to Your Git Provider">
    <Tabs
      groupId="git-provider"
      values={[
        { label: "GitHub", value: "github" },
        { label: "GitLab", value: "gitlab" },
        { label: "Bitbucket", value: "bitbucket" },
      ]}
    >
      <TabItem value="github">
        1. Go to your GitHub repository
        2. Navigate to **Settings** → **Secrets and variables** → **Actions**
        3. Click **New repository secret**
        4. Set the name to `WIDGETBOOK_CLOUD_API_KEY`
        5. Set the value to your API key from Widgetbook Cloud
        6. Click **Add secret**
      </TabItem>
      
      <TabItem value="gitlab">
        1. Go to your GitLab repository
        2. Navigate to **Settings** → **CI/CD**
        3. Expand the **Variables** section
        4. Click **Add variable**
        5. Set the key to `WIDGETBOOK_CLOUD_API_KEY`
        6. Set the value to your API key from Widgetbook Cloud
        7. Make sure **Protect variable** is checked if you want to restrict it to protected branches
        8. Click **Add variable**
      </TabItem>
      
      <TabItem value="bitbucket">
        1. Go to your Bitbucket repository
        2. Navigate to **Repository settings** → **Pipelines** → **Repository variables**
        3. Click **Add**
        4. Set the name to `WIDGETBOOK_CLOUD_API_KEY`
        5. Set the value to your API key from Widgetbook Cloud
        6. Make sure **Secured** is checked
        7. Click **Add**
      </TabItem>
    </Tabs>
  </Step>

  <Step title="Re-run the CI/CD Pipeline">
    <Tabs
      groupId="git-provider"
      values={[
        { label: "GitHub", value: "github" },
        { label: "GitLab", value: "gitlab" },
        { label: "Bitbucket", value: "bitbucket" },
      ]}
    >
      <TabItem value="github">
        1. Go to your GitHub repository
        2. Navigate to **Actions** tab
        3. Enable GitHub Actions for the forked repository
        4. Go to the Widgetbook Cloud Action and run it. 
        5. Wait for the workflow to complete
      </TabItem>
      
      <TabItem value="gitlab">
        1. Go to your GitLab repository
        2. Navigate to **Build** → **Pipelines**
        3. Find the most recent pipeline
        4. Click the **Retry** button (circular arrow icon)
        5. Wait for the pipeline to complete
      </TabItem>
      
      <TabItem value="bitbucket">
        1. Go to your Bitbucket repository
        2. Navigate to **Pipelines**
        3. Find the most recent pipeline
        4. Click the **Rerun** button
        5. Wait for the pipeline to complete
      </TabItem>
    </Tabs>
    
    <Info>
      After the pipeline completes successfully, you should see a new build appear in your Widgetbook Cloud project's builds section.
    </Info>
  </Step>

  <Step title="Create a Component Change and Pull Request">
    1. Create a new branch for your changes:

    ```bash
    git checkout -b feature/update-border-radius
    ```

    2. Change the border radius `full` in the `lib/ui/foundation/radius.dart` file to `0`.

    ```dart
    class DesignSystemRadius {
      static const double none = 0;
      static const double xs = 4;
      static const double sm = 8;
      static const double md = 12;
      static const double lg = 16;
      static const double xl = 28;
      static const double xxl = 32;
      static const double full = 999; // [!code --]
      static const double full = 0; // [!code ++]
    }

    ```

    3. Commit and push your changes:

    ```bash
    git add .
    git commit -m "Update radius"
    git push origin feature/update-border-radius
    ```

    4. Create a pull request (or merge request in GitLab):
    
    <Tabs
      groupId="git-provider"
      values={[
        { label: "GitHub", value: "github" },
        { label: "GitLab", value: "gitlab" },
        { label: "Bitbucket", value: "bitbucket" },
      ]}
    >
      <TabItem value="github">
        1. Go to your GitHub repository
        2. You should see a prompt to create a pull request for your recently pushed branch
        3. Make sure to target your forked repository as the base, not the original repository
        4. Click **Compare & pull request**
        5. Add a title and description for your PR
        6. Click **Create pull request**
      </TabItem>
      
      <TabItem value="gitlab">
        1. Go to your GitLab repository
        2. You should see a prompt to create a merge request for your recently pushed branch
        3. Click **Create merge request**
        4. Add a title and description for your MR
        5. Click **Create merge request**
      </TabItem>
      
      <TabItem value="bitbucket">
        1. Go to your Bitbucket repository
        2. Navigate to **Pull requests**
        3. Click **Create pull request**
        4. Select your feature branch as the source and main as the target
        5. Add a title and description
        6. Click **Create**
      </TabItem>
    </Tabs>
  </Step>

  <Step title="Check the Pull Request in Widgetbook Cloud">
    1. Wait for the CI/CD pipeline to complete for your feature branch
    2. Go to your Widgetbook Cloud project
    3. Navigate to the **Pull Requests** section
    4. You should see a new pull request
    5. Click on the pull request to see the visual differences between the base and feature branches
    6. Review the changes and approve or request changes as needed
    
    <Info>
      The review will show you exactly what changed visually between your feature branch and the main branch, making it easy to spot UI regressions or unintended changes.
    </Info>
  </Step>
</Steps>

## What's Next?

Congratulations! You've successfully set up a demo project with Widgetbook Cloud. Here are some next steps you can explore:

<CardGroup>
  <Card title="Learn about Reviews" icon="code-compare" href="/cloud/reviews">
    Discover how to use Widgetbook Cloud reviews to catch UI regressions before they reach production.
  </Card>
  
  <Card title="Explore Builds" icon="hammer" href="/cloud/builds/overview">
    Learn more about how Widgetbook Cloud builds work and how to access them.
  </Card>
  
  <Card title="Setup Your Own Project" icon="rocket" href="/cloud/projects/create">
    Apply what you've learned to set up Widgetbook Cloud with your own Flutter project.
  </Card>
</CardGroup>
```

### Connect and disconnect project

Source: https://docs.widgetbook.io/cloud/projects/connect

```mdx
# Connect and disconnect project

Connected projects automatically synchronize builds with their commit status and link reviews with their associated pull requests, enhancing Widgetbook Cloud's integration into your workflow.

## Connect project

<Warning>
  The user connecting the project must have **admin rights** to the git
  repository (i.e. has access to repository settings).
</Warning>

<Image src="/assets/cloud/projects/connect.png" zoom />

If you have [manually created a project](/cloud/projects/create#create-project-manually) or [disconnected a project](/cloud/projects/connect#disconnect-project), you can connect it to your Git repository to enable review synchronization.

To connect a project with a repository 
1. go to `Project > Settings > Git`.
1. select the Git provider of your choice
1. press `Import` on the repository you want to connect.

<Info>If the repository does not show up, use the search input box to filter the shown repositories.</Info>

## Disconnect project

<Image src="/assets/cloud/projects/disconnect.png" zoom />

In some situations you might want to disconnect a project from a Git repository.

To disconnect a project from a repository
1. go to `Project > Settings > Git`.
1. press `Disconnect` on the repository you want to disconnect.

## FAQ

Not finding what you're looking for? Check out our [Project-related FAQ](/cloud/projects/overview#faq).
```

### Create project

Source: https://docs.widgetbook.io/cloud/projects/create

```mdx
# Create project

To create a new project, click the `Create project` button on the workspace page. 
You will be prompted to create a project from a Git repository. 
Alternatively, you can [create a project manually](/cloud/projects/create#create-project-manually).

## Create project from Git repository

<Warning>
  The user creating the project must have **admin rights** to the git repository
  (i.e. has access to repository settings).
</Warning>

<Image src="/assets/cloud/projects/create-from-git.png" zoom />

Creating a project from a Git repository is the most common and recommended method. 
A connected project automatically synchronizes builds with their commit status and links reviews with their associated pull requests, enhancing Widgetbook Cloud's integration into your workflow.

We support the following Git providers:
- GitHub
- GitLab
- Bitbucket
- Azure

To create a project from a Git repository, connect your Git provider account and select the repository you'd like to use for the project. 

The project name is automatically set based on the repository name. 
For example, if your repository is called `acme-app`, the project will also be named `acme-app`. 
You can rename the project in the settings without affecting project synchronization.

<Info>If the repository does not show up, use the search input box to filter the shown repositories.</Info>

### Monorepo

If you want to create multiple projects (e.g., for a design system and several apps) from a monorepo, ensure that all projects are created from the same repository. 
Widgetbook Cloud supports mapping multiple projects to a single repository.

## Create project manually

<Warning>
Manually created projects do not support pull request synchronization and Widgetbook Cloud reviews — a feature highly appreciated by our customers.
</Warning>

If you're unable to connect your Git repository (e.g., due to permission issues or other reasons), creating a project manually is a good alternative to get started. 
You can always [connect your project to a repository](/cloud/projects/connect#connect-project) later on.

To create a project manually, click the `Create a new project yourself` link above the Git provider selector and enter the project name.

## FAQ

Not finding what you're looking for? Check out our [Project-related FAQ](/cloud/projects/overview#faq).
```

### Projects

Source: https://docs.widgetbook.io/cloud/projects/overview

```mdx
# Projects

Projects are the central hub for your Widgetbook builds, pull requests, and reviews.

<Image src="/assets/cloud/projects/list.png" zoom />

## Overview

A workspace is organized into different projects. 
It's up to you how you choose to divide your projects.

Consider grouping projects by:
- Design systems (Flutter packages)
- Applications (Flutter apps)
- Repositories

<Info>
If you're unsure how to divide your repository into Widgetbook projects, start with a single project. 
It's easy to add and migrate to a more refined project structure later.
</Info>

## Your projects

<Image src="/assets/cloud/projects/overview.png" zoom />

Your projects are displayed on the [workspace dashboard](https://app.widgetbook.io).

Each project includes the following tabs to help organize your data in Widgetbook Cloud:

- **[Builds](/cloud/builds/overview)**: A list of builds, each representing a commit of a Widgetbook version.
- **[Pull requests](/cloud/reviews)**: A list of synchronized pull requests from your Git repository, reflecting UI changes.
- **Settings**: Access and manage project configurations.

## Project settings

<Warning>Only workspace owners can access project settings.</Warning>

To access the settings, select a project within the current workspace and click `Settings`. 
The following settings categories and operations are available:

General
- Change the project name
- Fetch and regenerate the API key
- Specify the project's default branch
- Delete the project

Git
- Manage the connection to your Git repository

### Delete workspace

Deleting a project removes build, pull request, and review data. 
The owner can delete the project under `Settings > General > Delete project`.
This action is not reversible.

## FAQ

<Accordion title="How can I make my workspace or project public?">
As of now, it is not possible to make a workspace or project public. However, we are planning to add this feature soon.
</Accordion>
<Accordion title="I do not see my repository. What can I do?">
If you do not see your repository in the list of available repositories, ensure the following:
1. Your user has the necessary permissions to access the repository. 
1. If you're using GitHub, make sure you have granted the GitHub app the necessary permissions to access your repositories. For more information, refer to the [GitHub documentation](https://docs.github.com/en/apps/using-github-apps/reviewing-and-modifying-installed-github-apps#modifying-repository-access).
1. Due to technical reasons, Widgetbook Cloud only shows the 5 most recently accessed repositories. If you want to connect a repository that is not listed, you can use the search input box to filter the shown repositories. Make sure the repository is properly spelled. The search is case-sensitive!
</Accordion>
```

### Figma Reviews

Source: https://docs.widgetbook.io/cloud/reviews/figma

```mdx
# Figma Reviews

Widgetbook Cloud Review helps verify that developers meet **design requirements** by comparing **Flutter widgets** to their corresponding **Figma designs**.

<Image src="/assets/cloud/figma-review.png" zoom />

### Guide

To display a "View in Figma" button in your reviews, add the Figma URL to your use cases:

1. In your Figma design file, navigate to the component that matches your widget.
1. Copy the link to the component by right-clicking and selecting `Copy/Paste as` > `Copy Link`.
1. Set the `designLink` property of the `@UseCase` annotation by pasting the copied link.

   ```dart
   // Example from https://github.com/widgetbook/groceries-demo/blob/main/widgetbook/lib/core/app_bar.dart
   @UseCase(
     designLink: 'https://www.figma.com/file/EXuEpwiyksLAejYX1qr1v4/Fluttercon-Berlin-2023-Demo?type=design&node-id=277-3056&mode=design&t=nVL8hLmc1jlcilZL-4',
     name: 'Default',
     type: AppBar
   )
   ```

1. Re-run `build_runner` to update the use case metadata:

   ```bash
   dart run build_runner build -d
   ```

1. Commit your changes and push them to your repository to create a new build.
```

### Limitations

Source: https://docs.widgetbook.io/cloud/reviews/limitations

```mdx
# Limitations

## Animations

<Info>
  The animation limitation has been addressed in the upcoming Widgetbook v4. For
  more details, check out our [v4 testing
  guide](https://docs.widgetbook.io/~v4/testing).
</Info>

If your use cases include animations (e.g., `CircularProgressIndicator`), visual comparison may not work as expected.
The visual comparison algorithm cannot accurately compare animations.

We suggest the following workarounds:

1. **Adaptive animations**: you might want to still enjoy your animations while running Widgetbook, but you just need to disable them when the use-case is being used in a Widgetbook-Cloud-context. To do so, you can use the following `extension`, to make your use-cases adaptive to the Widgetbook Cloud Reviews and disable the animations during the visual comparison.

   ```dart
   extension WidgetbookContext on BuildContext {
     /// The `preview` query parameter is used while taking snapshots of the
     /// use-case for Widgetbook Cloud Reviews. This getter can be used to
     /// customize the behavior of the use-case while taking the snapshots,
     /// for example, to disable animations.
     bool get isInWidgetbookCloud => WidgetbookState.of(this).previewMode;
   }

   @UseCase(name: 'Default', type: CircularProgressIndicator)
   Widget adaptiveAnimationUseCase(BuildContext context) {
     return CircularProgressIndicator(
       value: context.isInWidgetbookCloud ? 0.5 : null,
     );
   }
   ```

2. **Start animations on demand**: Use a `boolean` knob to control your animations. This allows you to disable animations during visual comparison but still see them in Widgetbook.

   ```dart
   @UseCase(name: 'Default', type: CircularProgressIndicator)
   Widget onDemandAnimationUseCase(BuildContext context) {
     return CircularProgressIndicator(
       value: context.knobs.boolean(label: 'Static') ? 0.5 : null,
     );
   }
   ```

## Random Values

If your use case displays random values (e.g., dates) that differ across builds, visual comparison may produce noisy diffs.

We suggest the following workarounds:

1. Use a **constant value** instead of a random value.

   ```dart
   @UseCase(name: 'Default', type: Text)
   Widget constantValueUseCase(BuildContext context) {
     return Text(Random().nextInt(10).toString()); // [!code --]
     return Text('10'); // [!code ++]
   }
   ```

2. Use [**Knobs**](/knobs/overview) with a default value.

   ```dart
   @UseCase(name: 'Default', type: Text)
   Widget constantValueUseCase(BuildContext context) {
     return Text(Random().nextInt(10).toString()); // [!code --]
     return Text( // [!code ++]
       context.knobs.string(label: 'value', defaultValue: '10'), // [!code ++]
     ); // [!code ++]
   }
   ```
```

### Request a Reviewer

Source: https://docs.widgetbook.io/cloud/reviews/request-reviewer

```mdx
# Request a Reviewer

<Warning>
  This feature is currently in **beta**.
  Please report any issues you encounter and provide feedback regarding improvements.
</Warning>

Invite teammates to participate in a Widgetbook Cloud visual pull request by requesting them as reviewers. 
Requested reviewers are added to the reviewers list with a status of `Pending` until they submit their decision.

### How to request reviewers

<Steps>
  <Step title="Open the reviews context menu">
    Open the visual pull request on Widgetbook Cloud and open the reviews context menu.

    <Image src="/assets/cloud/reviews/reviews-context.png" caption="Open the reviews context menu on a visual pull request" zoom />
  </Step>
  <Step title="Pick teammates">
    Click the "Request reviewers" field to open the user selector. 
    Search by name and select one or more teammates.
    
    If your project has [required reviewers](/cloud/reviews/required-reviewer) configured, they will be indicated in the dropdown.

    <Image src="/assets/cloud/reviews/reviewers-assigned.png" caption="Select one or more teammates in the user selector" zoom />

    <Warning>
      Right now, requested reviewers cannot be removed.
      We're working on adding this functionality.
    </Warning>
  </Step>
  <Step title="Confirm and notify">
    Close the menu. 
    The selected users are added as requested reviewers and shown with a `Pending` status until they complete their review.

    <Image src="/assets/cloud/reviews/reviewers-pending.png" caption="Requested reviewers appear with a Pending status" zoom />
  </Step>
</Steps>
```

### Required reviewers

Source: https://docs.widgetbook.io/cloud/reviews/required-reviewer

```mdx
# Required reviewers 

<Warning>
  This feature is currently in **beta**.
  Please report any issues you encounter and provide feedback regarding improvements.
</Warning>

Due to the hierarchical nature of some teams, Widgetbook Cloud allows you to designate certain reviewers as required for visual pull requests. 
Therefore, this feature is great to ensure that specific team members, such as leads or designers, review and approve changes before they are merged.

When one or multiple required reviewers are defined, the approval of **at least one** of them is mandatory before the visual pull request is considered approved and can be merged.

## Enable required reviewers

<Info>
  Only workspace owners can access the Settings tab and configure required reviewers.
</Info>

<Image src="/assets/cloud/reviews/required-reviewer-setting.png" caption="Required reviewer setting" zoom />

<Steps>
  <Step title="Open project settings">
    Navigate to your project on Widgetbook Cloud and click on the "Settings" tab.

    Select the "General" section from the left sidebar.
  </Step>
  <Step title="Enable the feature">
    Locate the "Require approval from selected reviewers" checkbox and enable it.

    When enabled, at least one approval from the selected users is required to merge visual pull requests.
  </Step>
  <Step title="Select required reviewers">
    Click the "Select required assignees" field to open the user selector.
    Search by name and select one or more teammates who should be required reviewers.
  </Step>
  <Step title="Save changes">
    Click the "Save Changes" button at the bottom of the page to apply your settings.
  </Step>
</Steps>

## Pull request approval with required reviewers

When required reviewers are configured for a project, the approval status of a visual pull request is determined by the combination of reviews from both required and non-required reviewers.

### Review status logic

The visual pull request will be marked as **Approved** only when **both** of the following conditions are met:

1. At least one required reviewer has approved the visual pull request
2. No reviewer (required or non-required) has requested changes

This means that:

- Approvals from non-required reviewers alone are not sufficient to approve the pull request. At least one required reviewer must approve.
- A single "Request changes" review blocks approval, regardless of whether it comes from a required or non-required reviewer.
- Multiple required reviewers can be configured, but only one of them needs to approve (unless someone requests changes).
```

### Submit a Review

Source: https://docs.widgetbook.io/cloud/reviews/submit

```mdx
# Submit a Review

Widgetbook Cloud provides a platform for submitting reviews based on visual diffs, enabling teams to review and discuss visual changes in their projects.

## Review Changes

<Image src="/assets/cloud/visual-pull-request/example.png" zoom />

Use the diff view, with the old version on the left and the new version on the right, to review changes in your project.

## Publish a Review

<Image src="/assets/cloud/reviews/example.png" zoom />

Click the large blue "Review changes" button in the top-right corner to publish your review and set a review status.

The available statuses are:
- **Approved**: The changes are approved and can be merged. If integrated with a Git provider, this sets the commit status to successful, allowing the pull request to be merged.
- **Comment**: The changes are neither approved nor rejected, but you want to leave feedback for the author. If integrated with a Git provider, Widgetbook Cloud sets the commit status to pending, unless overridden by another status.
- **Changes requested**: The changes are not approved and must be revised before merging. If integrated with a Git provider, this sets the commit status to failed, preventing the pull request from being merged.

<Info>
  If your project has [required reviewers](/cloud/reviews/required-reviewer) configured, at least one required reviewer must approve before the visual pull request is considered approved.
</Info>

## Mark Changes as Viewed

<Image src="/assets/cloud/reviews/mark-as-viewed.png" zoom />

Click the "mark as viewed" button in the top-right of a use case diff to mark changes as reviewed.
If you are iterating on a pull request, the diff will remain marked as viewed as long as no new changes are introduced. 
When changes occur, the viewed state resets and you must review the changes again.

This feature helps reviewers track which changes have already been reviewed, improving iteration speed and reducing confusion.

## Visualize Changes

<Image src="/assets/cloud/reviews/show-diff.png" zoom />

Click the "Show diff" button in the top-right corner to visualize the differences between the old and new versions.
Changes are highlighted in pink, making it easy to spot even small differences.

## Add Comments

<Image src="/assets/cloud/reviews/add-comment.png" zoom />

Use the comment section of a use case diff to provide detailed feedback to the author of the pull request.
```

### Multi Snapshot Reviews

Source: https://docs.widgetbook.io/cloud/snapshots/multi-snapshot

```mdx
# Multi Snapshot Reviews

If you are using [Widgetbook Cloud Review](/cloud/reviews), you can now create multiple snapshots for a single use case. 
This feature is useful for testing different states of your use case using various addon or knob configurations, rather than just the default configuration.

<YouTube id="GJGGhUTT7LE?start=2941&end=3077" />

## Multi Snapshot for Addons

1. Open the main file of your app where your Widgetbook's `@App` annotation is defined.
1. Select the addons you want to test and include them, such as Themes and Languages. The following `AddonConfig` classes are available:

   - [`AlignmentAddonConfig`](/addons/alignment-addon#multi-snapshot-support)
   - [`LocalizationAddonConfig`](/addons/localization-addon#multi-snapshot-support)
   - [`SemanticsAddonConfig`](/addons/semantics-addon#multi-snapshot-support) 🧪
   - [`ThemeAddonConfig`](/addons/theme-addon#multi-snapshot-support)
   - [`TextScaleAddonConfig`](/addons/text-scale-addon#multi-snapshot-support)
   - [`ViewportAddonConfig`](/addons/viewport-addon#multi-snapshot-support)
   - [`ZoomAddonConfig`](/addons/zoom-addon#multi-snapshot-support)
   - [Custom Addon Config](/addons/custom-addon#multi-snapshot-support)

1. Add the configurations matrix to the `cloudAddonsConfigs` parameter of the `@App` annotation. For example, to test German in dark mode and English in light mode, both on an iPhone 12 viewport:

   ```dart
   @App(
     cloudAddonsConfigs: {
       'German Dark': [
         ViewportAddonConfig(IosViewports.iPhone12),
         LocalizationAddonConfig('de'),
         ThemeAddonConfig('Dark'),
       ],
       'English Light': [
         ViewportAddonConfig(IosViewports.iPhone12),
         LocalizationAddonConfig('en'),
         ThemeAddonConfig('Light'),
       ],
     },
   )
   class WidgetbookApp extends StatelessWidget {
     const WidgetbookApp({super.key});

     @override
     Widget build(BuildContext context) {
       return Widgetbook.material(...);
     }
   }
   ```

## Multi Snapshot for Knobs

1. Open the file where your use case is defined.
1. Add the configurations matrix to the `cloudKnobsConfigs` parameter of the `@UseCase` annotation. For example, to test two common variants of the use case:

```dart
@UseCase(
  name: 'Default',
  type: Button,
  cloudAddonsConfigs: {
    'Disabled Long Text Without Badge': [
      BooleanKnobConfig('enabled', false),
      StringKnobConfig('text', 'This is a very very long text for a button that might cause overflow'),
      NullKnobConfig('badge'),
    ],
    'Enabled Short Text With Badge': [
      BooleanKnobConfig('enabled', true),
      StringKnobConfig('text', 'Button'),
      IntKnobConfig('badge', 1),
    ],
  },
)
Widget buildButtonUseCase(BuildContext context) {
  return Button(
    text: context.knobs.string(label: 'text'),
    enabled: context.knobs.boolean(label: 'enabled'),
    badge: context.knobs.intOrNull(label: 'badge'),
  );
}
```
```

### Snapshots

Source: https://docs.widgetbook.io/cloud/snapshots/overview

```mdx
# Snapshots

Snapshots is the billing unit of the builds in Widgetbook Cloud. A snapshot is a [addon configuration](/cloud/snapshots/multi-snapshot#multi-snapshot-for-addons) and [knob configuration](/cloud/snapshots/multi-snapshot#multi-snapshot-for-knobs) for a use-case inside a build. For example:

- A `Button` use-case can have a "dark" and a "light" configuration when using the [Theme addon](/addons/theme-addon).
- A `Button` use-case can have a "long text" and "short text" configuration when using the [string knob](/knobs/string).

In short, the snapshots is a **matrix** between addons configs and knobs configs for each use-case in a build.

| <Image src="/assets/cloud/snapshots/button-dark-short-text.png" zoom caption="Dark x Short Text"  /> | <Image src="/assets/cloud/snapshots/button-light-short-text.png" zoom caption="Light x Short Text" /> |
| ---------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- |
| <Image src="/assets/cloud/snapshots/button-dark-long-text.png" zoom caption="Dark x Long Text" />    | <Image src="/assets/cloud/snapshots/button-light-long-text.png" zoom caption="Light x Long Text" />   |

## Default Configuration

When you create a build, Widgetbook Cloud will automatically create a default configuration for each use-case. This configuration is the one that is shown when the use-case is first opened in Widgetbook. If you want to add more configurations, you can do so by using the [Multi Snapshot feature](/cloud/snapshots/multi-snapshot).

## How are snapshots counted?

To estimate your snapshots count per build, you can use the following formula:

```
Snapshots = Knobs configs * Addons configs
```

For example, if your build has:

- 3 use-cases:

  1. `Button` - 2 knobs configs (`short text` and `long text`)
  2. `Card` - 0 knob config _(default configuration is counted as 1)_
  3. `Avatar` - 1 knob config (`no image`)

- 2 addons configs:

  1. `Dark German` - Dark theme with German Locale
  2. `Light English` - Light theme with English Locale

This means that the number of snapshots per build will be as follows:

```
Knobs configs = 2 (Button) + 1 (Card) + 1 (Avatar) = 4
Addons configs = 2 (Dark German and Light English)
Snapshots = 4 * 2 = 8
```

## Exclude Use-cases

If you have some use-cases that you don't want Widgetbook Cloud to process the snapshots for, you can exclude them as follows:

```dart
@UseCase(
  name: 'Default',
  type: UnstableWidget,
  cloudExclude: true // [!code highlight]
)
Widget buildUseCase(BuildContext context) { ... }
```
```

### Create Visual Pull Requests

Source: https://docs.widgetbook.io/cloud/visual-pull-request/create

```mdx
# Create Visual Pull Requests

Visual pull requests in Widgetbook Cloud are automatically created whenever a Widgetbook project is connected to a Git provider's repository and a code-based pull request is opened.

## Requirements

To use visual pull requests:
- [Connect your Widgetbook Cloud project with your Git provider's repository](/cloud/projects/connect#connect-project)
- Successfully [upload builds](/cloud/builds/upload#upload-build-using-cicd) for both the base and head commits of the pull request

## How Visual Pull Requests Connect with Builds

Pull requests in your Git provider and in Widgetbook operate on a base and head commit. 
The base commit is the commit from which the feature branch was created. 
The head commit contains the most recent changes in the feature branch.

<Image src="/assets/cloud/visual-pull-request/pr-visualisation-light.png" theme="light" caption="PR #1 aims to merge head commit z into base commit a by creating merge commit m" />
<Image src="/assets/cloud/visual-pull-request/pr-visualisation-dark.png" theme="dark" caption="PR #1 aims to merge head commit z into base commit a by creating merge commit m" />

To calculate the visual changes in a pull request, Widgetbook Cloud needs snapshots for both the base and head commits. 
These snapshots are provided by [uploading builds](/cloud/builds/upload#upload-build-using-cicd) to Widgetbook Cloud.

## Missing Builds

If a visual pull request is missing builds, Widgetbook Cloud will display a warning in the pull request overview.

<Image src="/assets/cloud/visual-pull-request/missing-build.png" caption="The pull request is missing builds for both the base and head commits." />

Follow the instructions for [uploading builds](/cloud/builds/upload#upload-build-using-cicd) to ensure all necessary builds are available.

<Warning>
**Caution:**
Some Git providers, like GitHub, run their CI/CD pipeline on the merge commit `m` when triggered by a pull request.
This causes Widgetbook Cloud to miss the head commit `z` for the pull request, which may cause confusion during setup.
</Warning>
```

### Visual Pull Requests

Source: https://docs.widgetbook.io/cloud/visual-pull-request/overview

```mdx
# Visual Pull Requests

Unlike code-based pull requests, visual pull requests focus on visual changes in a project. 
They allow developers to review and discuss user interface changes, ensuring the application's visual aspects meet the desired standards.

<Image src="/assets/cloud/visual-pull-request/example.png" caption="Widgetbook Cloud - Visual Pull Requests" zoom />

## Review Challenges 

Developers often encounter the following challenges when reviewing visual changes:
- Traditional code-based pull requests do not provide a clear view of visual changes, making it difficult to assess their impact and increasing the risk of unwanted regressions.
- Teams struggle to communicate effectively about visual changes, leading to misunderstandings and misalignment.
- Providing stakeholders with an application to review visual changes is cumbersome and time-consuming.

## Solving the Problem

Widgetbook Cloud addresses these challenges by providing a dedicated platform for visual pull requests. Key features include:

- **Visual Diffing**: Automatically generates visual diffs for changes, making it easy to see the impact of code changes on the user interface.
- **Collaborative Review**: Facilitates discussions around visual changes, allowing team members to provide feedback directly on the visual representation.
- **Stakeholder Access**: Offers a user-friendly interface for stakeholders to review visual changes without complex setup.

By streamlining the process of reviewing visual changes, Widgetbook Cloud helps teams ship faster without UI regressions.

## Code vs. Visual Pull Requests

In a code-based pull request, teams look for bugs, performance issues, and code quality. 
In a visual pull request, teams look for visual differences between the design and the implementation, and work to prevent regressions between versions. 
Both types of reviews are important to ensure the code is correct and the design is implemented as expected.

|                | Code-based pull request                                  | Visual pull request                                                                                           |
| -------------- | -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- |
| **Purpose**    | Find bugs, performance issues, and ensure code quality   | Find visual differences between the design and the implementation, and prevent regressions between versions.  |
| **Comparison** | **Code changes** between `base` and `head` commits       | **Visual differences** between `base` and `head` [builds](/cloud/builds/overview)                             |
| **Tool**       | Version Control Systems (e.g. GitHub, GitLab)            | Widgetbook Cloud                                                                                              |
```

### Double Knob

Source: https://docs.widgetbook.io/knobs/double/input

```mdx
# Double Knob

The double input knob renders a text field in the Widgetbook UI where you can dynamically enter a double value for a widget property. 
This is particularly useful for properties that require a double input, such as a percentage or currency.

## Variants

The double input knob has two variants:
- `context.knobs.double.input()`: This variant allows you to enter a double value via a text field. It does not accept `null` values.
- `context.knobs.doubleOrNull.input()`: This variant allows you to enter a double value via a text field or set the value to `null`. It is useful when the property can be optional.

<Info>Widgetbook also offers support for slider-based double knobs which are documented on the [Double Slider Knob page](/knobs/double/slider).</Info>

## Properties

Besides the knob's [base properties](/knobs/overview#properties), the double input knob does not feature any additional properties.

## `context.knobs.double.input()`

### Example

<iframe 
  src="https://preview.widgetbook.io/#/?path=knobpreview/double-input-knob&panels=knobs" 
  width="100%"
  height="240px"
/>

### Usage

To use the double input knob, call the `context.knobs.double.input()` method.

```dart title="Example: Double Input Knob"
@UseCase(type: MyWidget, name: 'Default')
Widget buildUseCase(BuildContext context) {
  return MyWidget(
    value: context.knobs.double.input(label: 'value', initialValue: 0.2), // [!code highlight]
  );
}
```

## `context.knobs.doubleOrNull.input()` 

### Example

<iframe 
  src="https://preview.widgetbook.io/#/?path=knobpreview/double-input-nullable-knob&panels=knobs" 
  width="100%"
  height="240px"
/>

### Usage

To use the nullable double input knob, call the `context.knobs.doubleOrNull.input()` method.

```dart title="Example: Nullable Double Input Knob"
@UseCase(type: MyWidget, name: 'Default')
Widget buildUseCase(BuildContext context) {
  return MyWidget(
    value: context.knobs.doubleOrNull.input(label: 'value'),
  );
}
```

## Multi-snapshot Support

Multi-snapshot support for Widgetbook Cloud allows you to generate multiple screenshots of a single use case with varying values using [KnobsConfigs](/cloud/snapshots/multi-snapshot#multi-snapshot-for-knobs) and [AddonsConfigs](/cloud/snapshots/multi-snapshot#multi-snapshot-for-addons).
Please refer to the [DoubleKnobConfig page](/knobs/double/overview#multi-snapshot-support) for more information.
```

### Double Knob

Source: https://docs.widgetbook.io/knobs/double/overview

```mdx
# Double Knob

The double knob renders a text field or slider in the Widgetbook UI where you can dynamically enter a double value for a widget property. 
This is particularly useful for properties that require a double input, such as a percentage or currency.

## Variants and Usage

The double knob has two variants with each a regular and a nullable type:
- [Double Input Knob](/knobs/double/input)
- [Double Slider Knob](/knobs/double/slider)

For the usage please refer to the respective pages.

## Example Input

<iframe 
  src="https://preview.widgetbook.io/#/?path=knobpreview/double-input-knob&panels=knobs" 
  width="100%"
  height="240px"
/>

## Example Slider

<iframe 
  src="https://preview.widgetbook.io/#/?path=knobpreview/double-slider-knob&panels=knobs" 
  width="100%"
  height="240px"
/>

## Multi-snapshot Support

Multi-snapshot support allows you to generate multiple screenshots of a single use case with varying values using [KnobsConfigs](/cloud/snapshots/multi-snapshot#multi-snapshot-for-knobs) and [AddonsConfigs](/cloud/snapshots/multi-snapshot#multi-snapshot-for-addons).

<Info>The blow information shows the `DoubleKnobConfig` for the [Double Input Knob](/knobs/double/input), but the `DoubleKnobConfig` can be applied to the [Double Slider Knob](/knobs/double/slider) as well.</Info>

### Regular double Knob

```dart title="Example: DoubleKnobConfig"
import 'package:flutter/material.dart';
import 'package:widgetbook/widgetbook.dart';
import 'package:widgetbook_annotation/widgetbook_annotation.dart';

@UseCase(
  type: MyWidget,
  name: 'Default',
  cloudKnobsConfigs: { // [!code highlight]
    '0%': [DoubleKnobConfig('value', 0)], // [!code highlight]
    '50%': [DoubleKnobConfig('value', 0.5)], // [!code highlight]
    '100%': [DoubleKnobConfig('value', 1)], // [!code highlight]
  }, // [!code highlight]
)
Widget buildUseCase(BuildContext context) {
  return MyWidget(
    value: context.knobs.double.input(label: 'value', initialValue: 0.2),
  );
}
```

### Nullable double knob

```dart title="Example: NullKnobConfig"
import 'package:flutter/material.dart';
import 'package:widgetbook/widgetbook.dart';
import 'package:widgetbook_annotation/widgetbook_annotation.dart';

@UseCase(
  type: MyWidget,
  name: 'Default',
  cloudKnobsConfigs: { // [!code highlight]
    'Without value': [NullKnobConfig('value')], // [!code highlight]
    'With value': [DoubleKnobConfig('value', 1)], // [!code highlight]
  }, // [!code highlight]
)
Widget buildUseCase(BuildContext context) {
  return MyWidget(
    value: context.knobs.doubleOrNull.input(label: 'value'),
  );
}
```
```

### Double Knob

Source: https://docs.widgetbook.io/knobs/double/slider

```mdx
# Double Knob

The double slider knob renders a slider in the Widgetbook UI where you can dynamically enter a double value for a widget property. 
This is particularly useful for properties that require a double input, such as a percentage or currency.

## Variants

The double slider knob has two variants:
- `context.knobs.double.slider()`: This variant allows you to enter a double value via a slider. It does not accept `null` values.
- `context.knobs.doubleOrNull.slider()`: This variant allows you to enter a double value via a slider or set the value to `null`. It is useful when the property can be optional.

<Info>Widgetbook also offers support for input-based double knobs which are documented on the [Double Input Knob page](/knobs/double/input).</Info>

## Properties

In addition to the knob's [base properties](/knobs/overview#properties), the double slider knob includes the following properties:

<Accordion title="context.knobs.double.slider()" defaultOpen>
  <Property name="min" type="int" optional>
    The minimum value the slider can select. This defines the start of the slider's range. 
    Defaults to `0`.
  </Property>

  ---

  <Property name="max" type="int" optional>
    The maximum value the slider can select. This defines the end of the slider's range. 
    Defaults to `20`.
  </Property>

  ---

  <Property name="divisions" type="int" optional>
    Sets how many evenly spaced discrete values are available between `min` and `max`. 
    When set, the slider snaps to these fixed intervals. 
    For example, with `min: 0`, `max: 10`, and `divisions: 5`, the selectable values will be: `0`, `2`, `4`, `6`, `8`, `10`. 
  </Property>
</Accordion>

## `context.knobs.double.slider()`

### Example

<iframe 
  src="https://preview.widgetbook.io/#/?path=knobpreview/double-slider-knob&panels=knobs" 
  width="100%"
  height="240px"
/>

### Usage

To use the double input knob, call the `context.knobs.double.slider()` method.

```dart title="Example: Double Slider Knob"
@UseCase(type: MyWidget, name: 'Default')
Widget buildUseCase(BuildContext context) {
  return MyWidget(
    value: context.knobs.double.slider( // [!code highlight]
      label: 'value', // [!code highlight]
      min: 0, // [!code highlight]
      max: 1, // [!code highlight]
      divisions: 10, // [!code highlight]
      initialValue: 0.2, // [!code highlight]
    ), // [!code highlight]
  );
}
```

## `context.knobs.doubleOrNull.slider()` 

### Example

<iframe 
  src="https://preview.widgetbook.io/#/?path=knobpreview/double-slider-nullable-knob&panels=knobs" 
  width="100%"
  height="240px"
/>

### Usage

To use the nullable double input knob, call the `context.knobs.doubleOrNull.input()` method.

```dart title="Example: Nullable Double Slider Knob"
@UseCase(type: MyWidget, name: 'Default')
Widget buildUseCase(BuildContext context) {
  return MyWidget(
    value: context.knobs.doubleOrNull.slider( // [!code highlight]
      label: 'value', // [!code highlight]
      min: 0, // [!code highlight]
      max: 1, // [!code highlight]
      divisions: 10, // [!code highlight]
    ), // [!code highlight]
  ); // [!code highlight]
}
```

## Multi-snapshot Support

Multi-snapshot support for Widgetbook Cloud allows you to generate multiple screenshots of a single use case with varying values using [KnobsConfigs](/cloud/snapshots/multi-snapshot#multi-snapshot-for-knobs) and [AddonsConfigs](/cloud/snapshots/multi-snapshot#multi-snapshot-for-addons).
Please refer to the [DoubleKnobConfig page](/knobs/double/overview#multi-snapshot-support) for more information.
```

### Integer Knob

Source: https://docs.widgetbook.io/knobs/integer/input

```mdx
# Integer Knob

The integer input knob renders a text field in the Widgetbook UI where you can dynamically enter an int value for a widget property. 
This is particularly useful for properties that require an integer input, such as shown in a notification badge.

## Variants

The integer input knob has two variants:
- `context.knobs.int.input()`: This variant allows you to enter an integer value via a text field. It does not accept `null` values.
- `context.knobs.intOrNull.input()`: This variant allows you to enter an integer value via a text field or set the value to `null`. It is useful when the property can be optional.

<Info>Widgetbook also offers support for slider-based integer knobs which are documented on the [Integer Slider Knob page](/knobs/integer/slider).</Info>

## Properties

Besides the knob's [base properties](/knobs/overview#properties), the integer input knob does not feature any additional properties.

## `context.knobs.int.input()`

### Example

<iframe 
  src="https://preview.widgetbook.io/#/?path=knobpreview/int-input-knob&panels=knobs" 
  width="100%"
  height="240px"
/>

### Usage

To use the integer input knob, call the `context.knobs.int.input()` method.

```dart title="Example: Integer Input Knob"
@UseCase(type: MyWidget, name: 'Default')
Widget buildUseCase(BuildContext context) {
  return MyWidget(
    count: context.knobs.int.input(label: 'count', initialValue: 100), // [!code highlight]
  );
}
```

## `context.knobs.intOrNull.input()` 

### Example

<iframe 
  src="https://preview.widgetbook.io/#/?path=knobpreview/int-input-nullable-knob&panels=knobs" 
  width="100%"
  height="240px"
/>

### Usage

To use the nullable integer input knob, call the `context.knobs.intOrNull.input()` method.

```dart title="Example: Nullable Integer Input Knob"
@UseCase(type: MyWidget, name: 'Default')
Widget buildUseCase(BuildContext context) {
  return MyWidget(
    count: context.knobs.intOrNull.input(label: 'count'), // [!code highlight]
  );
}
```

## Multi-snapshot Support

Multi-snapshot support for Widgetbook Cloud allows you to generate multiple screenshots of a single use case with varying values using [KnobsConfigs](/cloud/snapshots/multi-snapshot#multi-snapshot-for-knobs) and [AddonsConfigs](/cloud/snapshots/multi-snapshot#multi-snapshot-for-addons).
Please refer to the [IntKnobConfig page](/knobs/integer/overview#multi-snapshot-support) for more information.
```

### Integer Knob

Source: https://docs.widgetbook.io/knobs/integer/overview

```mdx
# Integer Knob

The integer knob renders a text field or slider in the Widgetbook UI where you can dynamically enter an integer value for a widget property. 
This is particularly useful for properties that require an integer input, such as a number for a notification badge.

## Variants and Usage

The integer knob has two variants with each a regular and a nullable type:
- [Integer Input Knob](/knobs/integer/input)
- [Integer Slider Knob](/knobs/integer/slider)

For the usage please refer to the respective pages.

## Example Input

<iframe 
  src="https://preview.widgetbook.io/#/?path=knobpreview/int-input-knob&panels=knobs" 
  width="100%"
  height="240px"
/>

## Example Slider

<iframe 
  src="https://preview.widgetbook.io/#/?path=knobpreview/int-slider-knob&panels=knobs" 
  width="100%"
  height="240px"
/>

## Multi-snapshot Support

Multi-snapshot support allows you to generate multiple screenshots of a single use case with varying values using [KnobsConfigs](/cloud/snapshots/multi-snapshot#multi-snapshot-for-knobs) and [AddonsConfigs](/cloud/snapshots/multi-snapshot#multi-snapshot-for-addons).

<Info>The blow information shows the `IntegerKnobConfig` for the [Integer Input Knob](/knobs/integer/input), but the `IntegerKnobConfig` can be applied to the [Integer Slider Knob](/knobs/integer/slider) as well.</Info>

### Regular integer knob

```dart title="Example: IntegerKnobConfig"
import 'package:flutter/material.dart';
import 'package:widgetbook/widgetbook.dart';
import 'package:widgetbook_annotation/widgetbook_annotation.dart';

@UseCase(
  type: MyWidget,
  name: 'Default',
  cloudKnobsConfigs: { // [!code highlight]
    'No notifications': [IntKnobConfig('count', 0)], // [!code highlight]
    '3 notifications': [IntKnobConfig('count', 3)], // [!code highlight]
    '99+ notifications': [IntKnobConfig('count', 3)], // [!code highlight]
  }, // [!code highlight]
)
Widget buildUseCase(BuildContext context) {
  return MyWidget(
    count: context.knobs.intOrNull.input(label: 'count'),
  );
}
```

### Nullable integer knob

```dart title="Example: NullKnobConfig"
import 'package:flutter/material.dart';
import 'package:widgetbook/widgetbook.dart';
import 'package:widgetbook_annotation/widgetbook_annotation.dart';

@UseCase(
  type: MyWidget,
  name: 'Default',
  cloudKnobsConfigs: { // [!code highlight]
    'Without count': [NullKnobConfig('value')], // [!code highlight]
    'With count': [IntKnobConfig('value', 1)], // [!code highlight]
  }, // [!code highlight]
)
Widget buildUseCase(BuildContext context) {
  return MyWidget(
    count: context.knobs.doubleOrNull.input(label: 'count'),
  );
}
```
```

### Integer Knob

Source: https://docs.widgetbook.io/knobs/integer/slider

```mdx
# Integer Knob

The integer slider knob renders a slider in the Widgetbook UI where you can dynamically enter an integer value for a widget property. 
This is particularly useful for properties that require an integer input, such as shown in a notification badge.

## Variants

The double slider knob has two variants:
- `context.knobs.int.slider()`: This variant allows you to enter an integer value via a slider. It does not accept `null` values.
- `context.knobs.intOrNull.slider()`: This variant allows you to enter an integer value via a slider or set the value to `null`. It is useful when the property can be optional.

<Info>Widgetbook also offers support for input-based integer knobs which are documented on the [Integer Input Knob page](/knobs/integer/input).</Info>

## Properties

In addition to the knob's [base properties](/knobs/overview#properties), the integer slider knob includes the following properties:

<Accordion title="context.knobs.int.slider()" defaultOpen>
  <Property name="min" type="int" optional>
    The minimum value the slider can select. This defines the start of the slider's range. 
    Defaults to `0`.
  </Property>

  ---

  <Property name="max" type="int" optional>
    The maximum value the slider can select. This defines the end of the slider's range. 
    Defaults to `20`.
  </Property>

  ---

  <Property name="divisions" type="int" optional>
    Sets how many evenly spaced discrete values are available between `min` and `max`. 
    When set, the slider snaps to these fixed intervals. For example, with `min: 0`, `max: 10`, and `divisions: 5`, the selectable values will be: `0`, `2`, `4`, `6`, `8`, `10`. 
    Defaults to `null`, which allows continuous values.
  </Property>
</Accordion>

## `context.knobs.int.slider()`

### Example

<iframe 
  src="https://preview.widgetbook.io/#/?path=knobpreview/int-slider-knob&panels=knobs" 
  width="100%"
  height="240px"
/>

### Usage

To use the integer input knob, call the `context.knobs.int.slider()` method.

```dart title="Example: Double Slider Knob"
@UseCase(type: MyWidget, name: 'Default')
Widget buildUseCase(BuildContext context) {
  return MyWidget(
    count: context.knobs.int.slider( // [!code highlight]
      label: 'count', // [!code highlight]
      min: 0, // [!code highlight]
      max: 100, // [!code highlight]
      divisions: 20, // [!code highlight]
      initialValue: 100, // [!code highlight]
    ), // [!code highlight]
  );
}
```

## `context.knobs.intOrNull.slider()` 

### Example

<iframe 
  src="https://preview.widgetbook.io/#/?path=knobpreview/int-slider-nullable-knob&panels=knobs" 
  width="100%"
  height="240px"
/>

### Usage

To use the nullable double input knob, call the `context.knobs.doubleOrNull.input()` method.

```dart title="Example: Nullable Double Slider Knob"
@UseCase(type: MyWidget, name: 'Default')
Widget buildUseCase(BuildContext context) {
  return MyWidget(
    count: context.knobs.intOrNull.slider( // [!code highlight]
      label: 'count', // [!code highlight]
      min: 0, // [!code highlight] 
      max: 100, // [!code highlight]
      divisions: 20, // [!code highlight]
    ), // [!code highlight]
  );
}
```

## Multi-snapshot Support

Multi-snapshot support for Widgetbook Cloud allows you to generate multiple screenshots of a single use case with varying values using [KnobsConfigs](/cloud/snapshots/multi-snapshot#multi-snapshot-for-knobs) and [AddonsConfigs](/cloud/snapshots/multi-snapshot#multi-snapshot-for-addons).
Please refer to the [Multi-snapshot Support page](/knobs/integer/overview#multi-snapshot-support) for more information about `IntegerKnobConfig`.
```

### Iterable Knob

Source: https://docs.widgetbook.io/knobs/iterable/overview

```mdx
# Iterable Knob

The iterable knob renders a segmented button in the Widgetbook UI where you can dynamically select an iterable value for a widget property.
This is particularly useful for properties that require an iterable input, such as a configuration iterable for a widget.

## Variants and Usage

The iterable knob has one variant with each a regular and a nullable type:

- [Iterable Segmented Knob](/knobs/iterable/segmented)

For the usage please refer to the respective pages.

## Example Segmented

<iframe
  src="https://preview.widgetbook.io/#/?path=knobpreview/iterable-segmented-knob&panels=knobs"
  width="100%"
  height="240px"
/>

## Multi-snapshot Support

Multi-snapshot support allows you to generate multiple screenshots of a single use case with varying values using [KnobsConfigs](/cloud/snapshots/multi-snapshot#multi-snapshot-for-knobs) and [AddonsConfigs](/cloud/snapshots/multi-snapshot#multi-snapshot-for-addons).

### Regular iterable knob

```dart title="Example: IterableKnobConfig"
@UseCase(
  type: MyWidget,
  name: 'Default',
  cloudKnobsConfigs: { // [!code highlight]
    'online and busy': [IterableKnobConfig('status', '[online,busy]')], // [!code highlight]
    'offline and busy': [IterableKnobConfig('status', '[offline,busy]')], // [!code highlight]
  }, // [!code highlight]
)
Widget buildUseCase(BuildContext context) {
  return MyWidget(
    status: context.knobs.iterable.segmented<OnlineStatusType>(
      label: 'status',
      labelBuilder: (value) => value.name,
      options: [
        OnlineStatusType.online,
        OnlineStatusType.offline,
        OnlineStatusType.busy,
      ],
    ),
  );
}

enum OnlineStatusType { online, offline, busy }
```

### Nullable iterable Knob

```dart title="Example: NullKnobConfig"
@UseCase(
  type: MyWidget,
  name: 'Default',
  cloudKnobsConfigs: { // [!code highlight]
    'no status': [NullKnobConfig('status')], // [!code highlight]
    'online and busy': [IterableKnobConfig('status', '[online,busy]')], // [!code highlight]
  }, // [!code highlight]
)
Widget buildUseCase(BuildContext context) {
  return MyWidget(
    status: context.knobs.iterableOrNull.segmented<OnlineStatusType>(
      label: 'status',
      labelBuilder: (value) => value.name,
      options: [
        OnlineStatusType.online,
        OnlineStatusType.offline,
        OnlineStatusType.busy,
      ],
    ),
  );
}

enum OnlineStatusType { online, offline, busy }
```
```

### Iterable Segmented Knob

Source: https://docs.widgetbook.io/knobs/iterable/segmented

```mdx
# Iterable Segmented Knob

The iterable segmented knob allows you to dynamically enter an iterable value for a widget property in the Widgetbook UI.
This is particularly useful for properties that require an iterable input, such as a `List` or `Set` of item.

## Variants

The iterable knob has two variants:

- `context.knobs.iterable.segmented()`: This variant allows you to enter an iterable value via a segmented button. It does not accept `null` values.
- `context.knobs.iterableOrNull.segmented()`: This variant allows you to enter an iterable value via a segmented button or set the value to `null`. It is useful when the property can be optional.

## Properties

In addition to the knob's [base properties](/knobs/overview#properties), the iterable segmented knob includes the following:

<Accordion title="context.knobs.iterable.segmented()" defaultOpen>
  <Property name="options" type="T" required>
    The options shown in the segmented buttons. The type of the options must be the same as the type of the property.
  </Property>

  <Property name="labelBuilder" type="LabelBuilder<T>" optional>
    A function to format the options for display in the segmented buttons.
  </Property>
</Accordion>

## `context.knobs.iterable.segmented()`

### Example

<iframe
  src="https://preview.widgetbook.io/#/?path=knobpreview/iterable-segmented-knob&panels=knobs"
  width="100%"
  height="240px"
/>

### Usage

To use the iterable segmented knob, call the `context.knobs.iterable.segmented()` method.

```dart title="Example: Iterable Segmented Knob"
@UseCase(type: MyWidget, name: 'Default')
Widget buildUseCase(BuildContext context) {
  return MyWidget(
    status: context.knobs.iterable.segmented( // [!code highlight]
      label: 'status', // [!code highlight]
      labelBuilder: (value) => value!.name, // [!code highlight]
      options: [ // [!code highlight]
        OnlineStatusType.online, // [!code highlight]
        OnlineStatusType.offline, // [!code highlight]
        OnlineStatusType.busy, // [!code highlight]
      ], // [!code highlight]
    ), // [!code highlight]
  );
}

enum OnlineStatusType { online, offline, busy }
```

## `context.knobs.iterableOrNull.segmented()`

### Example

<iframe
  src="https://preview.widgetbook.io/#/?path=knobpreview/iterable-segmented-nullable-knob&panels=knobs"
  width="100%"
  height="240px"
/>

### Usage

To use the nullable iterable segmented knob, call the `context.knobs.iterableOrNull.segmented()` method.

```dart title="Example: Nullable Iterable Segmented Knob"
@UseCase(type: MyWidget, name: 'Default')
Widget buildUseCase(BuildContext context) {
  return MyWidget(
    status: context.knobs.iterableOrNull.segmented(
      label: 'status',
      labelBuilder: (value) => value!.name,
      options: [
        OnlineStatusType.online,
        OnlineStatusType.offline,
        OnlineStatusType.busy,
      ],
    ),
  );
}

enum OnlineStatusType { online, offline, busy }
```

## Multi-snapshot Support

Multi-snapshot support for Widgetbook Cloud allows you to generate multiple screenshots of a single use case with varying values using [KnobsConfigs](/cloud/snapshots/multi-snapshot#multi-snapshot-for-knobs) and [AddonsConfigs](/cloud/snapshots/multi-snapshot#multi-snapshot-for-addons).
Please refer to the [IterableKnobConfig page](/knobs/iterable/overview#multi-snapshot-support) for more information.
```

### Object Dropdown Knob

Source: https://docs.widgetbook.io/knobs/object/dropdown

```mdx
# Object Dropdown Knob

The object knob allows you to dynamically enter an object value for a widget property in the Widgetbook UI.
This is particularly useful for properties that require an object input, such as a user profile or an enum value.

## Variants

The object knob has two variants:

- `context.knobs.object.dropdown()`: This variant allows you to enter an object value via a dropdown menu. It does not accept `null` values.
- `context.knobs.objectOrNull.dropdown()`: This variant allows you to enter an object value via a dropdown menu or set the value to `null`. It is useful when the property can be optional.

<Info>
  Widgetbook also offers support for segmented button object knobs which are
  documented on the [Object Segmented Knob page](/knobs/object/segmented).
</Info>

## Properties

In addition to the knob's [base properties](/knobs/overview#properties), the object dropdown knob includes the following:

<Accordion title="context.knobs.object.dropdown()" defaultOpen>
  <Property name="options" type="T" required>
    The options shown in the dropdown menu. The type of the options must be the same as the type of the property.
  </Property>

  <Property name="labelBuilder" type="LabelBuilder<T>" optional>
    A function to format the options for display in the dropdown menu.
  </Property>
</Accordion>

## `context.knobs.object.dropdown()`

### Example

<iframe
  src="https://preview.widgetbook.io/#/?path=knobpreview/object-dropdown-knob&panels=knobs"
  width="100%"
  height="240px"
/>

### Usage

To use the object dropdown knob, call the `context.knobs.object.dropdown()` method.

```dart title="Example: Object Dropdown Knob"
@UseCase(type: MyWidget, name: 'Default')
Widget buildUseCase(BuildContext context) {
  return MyWidget(
    status: context.knobs.object.dropdown( // [!code highlight]
      label: 'status', // [!code highlight]
      labelBuilder: (value) => value!.name, // [!code highlight]
      options: [ // [!code highlight]
        OnlineStatusType.online, // [!code highlight]
        OnlineStatusType.offline, // [!code highlight]
        OnlineStatusType.busy, // [!code highlight]
      ], // [!code highlight]
    ), // [!code highlight]
  );
}

enum OnlineStatusType { online, offline, busy }
```

## `context.knobs.objectOrNull.dropdown()`

### Example

<iframe
  src="https://preview.widgetbook.io/#/?path=knobpreview/object-dropdown-nullable-knob&panels=knobs"
  width="100%"
  height="240px"
/>

### Usage

To use the nullable object dropdown knob, call the `context.knobs.objectOrNull.dropdown()` method.

```dart title="Example: Nullable Object Dropdown Knob"
@UseCase(type: MyWidget, name: 'Default')
Widget buildUseCase(BuildContext context) {
  return MyWidget(
    status: context.knobs.objectOrNull.dropdown(
      label: 'status',
      labelBuilder: (value) => value!.name,
      options: [
        OnlineStatusType.online,
        OnlineStatusType.offline,
        OnlineStatusType.busy,
      ],
    ),
  );
}

enum OnlineStatusType { online, offline, busy }
```

## Multi-snapshot Support

Multi-snapshot support for Widgetbook Cloud allows you to generate multiple screenshots of a single use case with varying values using [KnobsConfigs](/cloud/snapshots/multi-snapshot#multi-snapshot-for-knobs) and [AddonsConfigs](/cloud/snapshots/multi-snapshot#multi-snapshot-for-addons).
Please refer to the [ObjectKnobConfig page](/knobs/object/overview#multi-snapshot-support) for more information.
```

### Object Knob

Source: https://docs.widgetbook.io/knobs/object/overview

```mdx
# Object Knob

The object knob renders a segmented button or a dropdown menu in the Widgetbook UI where you can dynamically select an object value for a widget property.
This is particularly useful for properties that require an object input, such as a configuration object for a widget, or an enum value.

## Variants and Usage

The object knob has one variant with each a regular and a nullable type:

- [Object Segmented Knob](/knobs/object/segmented)
- [Object Dropdown Knob](/knobs/object/dropdown)

For the usage please refer to the respective pages.

## Example Segmented

<iframe
  src="https://preview.widgetbook.io/#/?path=knobpreview/object-segmented-knob&panels=knobs"
  width="100%"
  height="240px"
/>

## Example Dropdown

<iframe
  src="https://preview.widgetbook.io/#/?path=knobpreview/object-dropdown-knob&panels=knobs"
  width="100%"
  height="240px"
/>

## Multi-snapshot Support

Multi-snapshot support allows you to generate multiple screenshots of a single use case with varying values using [KnobsConfigs](/cloud/snapshots/multi-snapshot#multi-snapshot-for-knobs) and [AddonsConfigs](/cloud/snapshots/multi-snapshot#multi-snapshot-for-addons).

<Info>
  The blow information shows the `ObjectKnobConfig` for the [Object Segmented
  Knob](/knobs/object/segmented), but the `ObjectKnobConfig` can be applied to the [Object Dropdown Knob](/knobs/object/dropdown) as well.
</Info>

### Regular object knob

```dart title="Example: ObjectKnobConfig"
@UseCase(
  type: MyWidget,
  name: 'Default',
  cloudKnobsConfigs: { // [!code highlight]
    'online': [ObjectKnobConfig('status', 'online')], // [!code highlight]
    'offline': [ObjectKnobConfig('status', 'offline')], // [!code highlight]
    'busy': [ObjectKnobConfig('status', 'busy')], // [!code highlight]
  }, // [!code highlight]
)
Widget buildUseCase(BuildContext context) {
  return MyWidget(
    status: context.knobs.object.segmented<OnlineStatusType>(
      label: 'status',
      labelBuilder: (value) => value.name,
      options: [
        OnlineStatusType.online,
        OnlineStatusType.offline,
        OnlineStatusType.busy,
      ],
    ),
  );
}

enum OnlineStatusType { online, offline, busy }
```

### Nullable object Knob

```dart title="Example: NullKnobConfig"
@UseCase(
  type: MyWidget,
  name: 'Default',
  cloudKnobsConfigs: { // [!code highlight]
    'no status': [NullKnobConfig('status')], // [!code highlight]
    'online': [ObjectKnobConfig('status', 'online')], // [!code highlight]
  }, // [!code highlight]
)
Widget buildUseCase(BuildContext context) {
  return MyWidget(
    status: context.knobs.objectOrNull.segmented<OnlineStatusType>(
      label: 'status',
      labelBuilder: (value) => value.name,
      options: [
        OnlineStatusType.online,
        OnlineStatusType.offline,
        OnlineStatusType.busy,
      ],
    ),
  );
}

enum OnlineStatusType { online, offline, busy }
```
```

### Object Segmented Knob

Source: https://docs.widgetbook.io/knobs/object/segmented

```mdx
# Object Segmented Knob

The object segmented knob allows you to dynamically enter an object value for a widget property in the Widgetbook UI.
This is particularly useful for properties that require an object input, such as a user profile or an enum value.

## Variants

The object knob has two variants:

- `context.knobs.object.segmented()`: This variant allows you to enter an object value via a segmented button. It does not accept `null` values.
- `context.knobs.objectOrNull.segmented()`: This variant allows you to enter an object value via a segmented button or set the value to `null`. It is useful when the property can be optional.

<Info>
  Widgetbook also offers support for dropdown menu object knobs which are
  documented on the [Object Dropdown Knob page](/knobs/object/dropdown).
</Info>

## Properties

In addition to the knob's [base properties](/knobs/overview#properties), the object segmented knob includes the following:

<Accordion title="context.knobs.object.segmented()" defaultOpen>
  <Property name="options" type="T" required>
    The options shown in the segmented buttons. The type of the options must be the same as the type of the property.
  </Property>

  <Property name="labelBuilder" type="LabelBuilder<T>" optional>
    A function to format the options for display in the segmented buttons.
  </Property>
</Accordion>

## `context.knobs.object.segmented()`

### Example

<iframe
  src="https://preview.widgetbook.io/#/?path=knobpreview/object-segmented-knob&panels=knobs"
  width="100%"
  height="240px"
/>

### Usage

To use the object segmented knob, call the `context.knobs.object.segmented()` method.

```dart title="Example: Object Segmented Knob"
@UseCase(type: MyWidget, name: 'Default')
Widget buildUseCase(BuildContext context) {
  return MyWidget(
    status: context.knobs.object.segmented( // [!code highlight]
      label: 'status', // [!code highlight]
      labelBuilder: (value) => value!.name, // [!code highlight]
      options: [ // [!code highlight]
        OnlineStatusType.online, // [!code highlight]
        OnlineStatusType.offline, // [!code highlight]
        OnlineStatusType.busy, // [!code highlight]
      ], // [!code highlight]
    ), // [!code highlight]
  );
}

enum OnlineStatusType { online, offline, busy }
```

## `context.knobs.objectOrNull.segmented()`

### Example

<iframe
  src="https://preview.widgetbook.io/#/?path=knobpreview/object-segmented-nullable-knob&panels=knobs"
  width="100%"
  height="240px"
/>

### Usage

To use the nullable object segmented knob, call the `context.knobs.objectOrNull.segmented()` method.

```dart title="Example: Nullable Object Segmented Knob"
@UseCase(type: MyWidget, name: 'Default')
Widget buildUseCase(BuildContext context) {
  return MyWidget(
    status: context.knobs.objectOrNull.segmented(
      label: 'status',
      labelBuilder: (value) => value!.name,
      options: [
        OnlineStatusType.online,
        OnlineStatusType.offline,
        OnlineStatusType.busy,
      ],
    ),
  );
}

enum OnlineStatusType { online, offline, busy }
```

## Multi-snapshot Support

Multi-snapshot support for Widgetbook Cloud allows you to generate multiple screenshots of a single use case with varying values using [KnobsConfigs](/cloud/snapshots/multi-snapshot#multi-snapshot-for-knobs) and [AddonsConfigs](/cloud/snapshots/multi-snapshot#multi-snapshot-for-addons).
Please refer to the [ObjectKnobConfig page](/knobs/object/overview#multi-snapshot-support) for more information.
```

### Access builds from Azure commit statuses

Source: https://docs.widgetbook.io/cloud/guides/azure/access-from-commit

```mdx
# Access builds from Azure commit statuses

Widgetbook Cloud links builds to [Azure commit statuses](https://learn.microsoft.com/en-us/rest/api/azure/devops/git/statuses) so accessing Widgetbook builds integrates seamlessly with your Azure workflow.

## Prerequisites

Linking builds to Azure commit statuses requires API access to your Azure repository.
By [connecting a Widgetbook project to a Azure repository](/cloud/projects/connect#connect-project) you allow Widgetbook Cloud to write commit statuses of your repository and add Widgetbook build links to commits.
Once you've connected your project, build linking via commit statuses is automatically enabled.

<Warning>
Commit statuses are only created for new builds after the project is connected to a Azure repository.
If you've uploaded builds before connecting the project, commit statuses are not created for these builds.
</Warning>

## How it works

Widgetbook Cloud adds a commit status to the commit of the uploaded build.
The commit status is a link to the build details page in Widgetbook Cloud and is accessed by clicking the "Widgetbook Build" commit status.
```

### cloud/guides/azure/enforce-reviews

Source: https://docs.widgetbook.io/cloud/guides/azure/enforce-reviews

```mdx
## Enforcing reviews

Depending on your goals and workflow, you may want to enforce reviews for all changes in your project, requiring an accepted Widgetbook review for each pull request with changes.

When Widgetbook Cloud reviews are enabled for your project, Widgetbook Cloud automatically creates a review and posts a pull request iteration status in Azure DevOps. 
If changes are requested, the iteration status reflects the Widgetbook review status on your PR.
However, Azure DevOps' default configuration allows PRs to be merged even if status checks are not passing or still pending.

To enforce reviews for all changes in your project, you can set up a branch policy in your Azure DevOps repository. 
This prevents PRs from being merged until the Widgetbook review is accepted.

<Steps>
  <Step title="Navigate to Project Settings">
    Go to your Azure DevOps project.
    Click on *Project Settings* in the bottom left corner (gear icon).
  </Step>
  <Step title="Open Repositories settings">
    In the Project Settings menu, under the *Repos* section, select *Repositories*.
  </Step>
  <Step title="Select repository">
    If you have a single repository per project, click the *Policies* tab.
    If you have multiple repositories, first select the specific repository you want to apply this rule to, then click the *Policies* tab.
  </Step>
  <Step title="Create branch policy">
    Next to *Branch Policies*, click the *+* button.
    Select *Protect the default branch of each repository* and click *Create*.
  </Step>
  <Step title="Add status check">
    Next to *Status Check*, click the *+* button.
    
    Enter the following information:
    - **Genre**: `Widgetbook`
    - **Name**: `Widgetbook Review (<widgetbook-project-name>)`
    
    Replace `<widgetbook-project-name>` with your actual Widgetbook project name. For example, if your project is named "Todo App", the name would be `Widgetbook Review (Todo App)`.
    
    You can find your project name in the navigation bar of Widgetbook Cloud when viewing your project or in the project's settings.
    <Info>
      Make sure to enter the genre and name separately in their respective fields.
    </Info>
  </Step>
  <Step title="Configure advanced settings">
    Expand the *Advanced* section.
    Enable the option *Reset status whenever there are new changes*.
    
    This ensures that the Widgetbook review status is reset when new commits are pushed to the PR.
  </Step>
  <Step title="Save the policy">
    Click *Save* to create the status check policy.
  </Step>
  <Step title="Verify policy">
    Navigate to a PR to verify that the Widgetbook Review status check is now required to merge the PR.
    The PR will be blocked from merging until the Widgetbook review is accepted.
  </Step>
</Steps>
```

### Widgetbook Cloud x Azure

Source: https://docs.widgetbook.io/cloud/guides/azure/upload

```mdx
# Widgetbook Cloud x Azure

If you want to use Widgetbook Cloud with your existing Azure DevOps project, here's a step-by-step guide to help you get started.

<Steps>
  <Step title="Setup Widgetbook Cloud Project">
    Create a new project in Widgetbook Cloud by importing your Azure repository.
  </Step>
  
  <Step title="Add API Key as a Secret">
    Add `WIDGETBOOK_API_KEY` to your [Azure DevOps project's secrets](https://docs.microsoft.com/en-us/azure/devops/pipelines/process/variables?view=azure-devops&tabs=yaml%2Cbatch#secret-variables). You can find the API key in the Widgetbook Cloud's **project settings page**.
  </Step>

  <Step title="Create Azure DevOps Pipeline">
    To upload a [Widgetbook Build](/cloud/builds/overview) for each new commit in your repository, you need to setup up a workflow in Azure DevOps Pipelines that runs on every push.

    Add the following workflow to your `azure-pipeline.yaml` file.

    ```yaml
    trigger:
      branches:
        include:
          - "*"

    pr:
      branches:
        include:
          - "*"

    stages:
      - stage: WidgetbookHostingStage
        displayName: Hosting
        jobs:
          - job: HostingJob
            displayName: Hosting
            pool:
              vmImage: "ubuntu-latest"
            steps:
              - task: FlutterInstall@0
                displayName: "Install Flutter SDK"
                inputs:
                  mode: "auto"
                  channel: "stable"
                  version: "latest"

              - task: Bash@3
                displayName: "Bootstrap App"
                inputs:
                  targetType: "inline"
                  script: |
                    $(FlutterToolPath)/flutter pub get
                    # Add any other steps needed to make your
                    # app widgets available for Widgetbook

              - task: Bash@3
                displayName: "Build Widgetbook"
                inputs:
                  workingDirectory: "widgetbook"
                  targetType: "inline"
                  script: |
                    $(FlutterToolPath)/flutter pub get
                    $(DartToolPath)/dart run build_runner build -d
                    $(FlutterToolPath)/flutter build web -t lib/main.dart

              - task: Bash@3
                displayName: "Install Widgetbook CLI"
                inputs:
                  targetType: "inline"
                  script: |
                    $(DartToolPath)/dart pub global activate widgetbook_cli

              - task: Bash@3
                displayName: "Push Widgetbook Build"
                inputs:
                  workingDirectory: "widgetbook"
                  targetType: "inline"
                  script: |
                    $HOME/.pub-cache/bin/widgetbook cloud build push --api-key $(WIDGETBOOK_API_KEY)
    ```

  </Step>

  <Step title="Create a Pull Request">
    To test out if everything works properly, create a new branch and push a commit to it, then submit a PR with the new branch.
    After the build upload finishes, a commit status will be added to your PR once the [Widgetbook Review](/cloud/reviews) is ready.

  </Step>

</Steps>

## Troubleshooting

To make sure that your Widgetbook Cloud setup works properly, you can check that your repository has a Webhook configured _(under Project Settings > Service Hooks)_ to notify Widgetbook Cloud about new PRs.
```

### Access builds from Bitbucket commit statuses

Source: https://docs.widgetbook.io/cloud/guides/bitbucket/access-from-commit

```mdx
# Access builds from Bitbucket commit statuses

Widgetbook Cloud links builds to Bitbucket commit statuses so accessing Widgetbook builds integrates seamlessly with your Bitbucket workflow.

## Prerequisites

Linking builds to Bitbucket commit statuses requires API access to your Bitbucket repository.
By [connecting a Widgetbook project to a Bitbucket repository](/cloud/projects/connect#connect-project) you allow Widgetbook Cloud to write commit statuses of your repository and add Widgetbook build links to commits.
Once you've connected your project, build linking via commit statuses is automatically enabled.

<Warning>
Commit statuses are only created for new builds after the project is connected to a Bitbucket repository.
If you've uploaded builds before connecting the project, commit statuses are not created for these builds.
</Warning>

## How it works

Widgetbook Cloud adds a commit status to the commit of the uploaded build.
The commit status is a link to the build details page in Widgetbook Cloud and is accessed by clicking the "Widgetbook Build" commit status.
```

### Widgetbook Cloud x Self-hosted Bitbucket

Source: https://docs.widgetbook.io/cloud/guides/bitbucket/self-hosted

```mdx
# Widgetbook Cloud x Self-hosted Bitbucket

If you want to use Widgetbook Cloud with your self-hosted Bitbucket instance (e.g. `bitbucket.acme.com`), here's a pre-requisite guide to help you get started, then you can follow our normal [Bitbucket setup guide](/cloud/guides/bitbucket/upload).

<Steps>
  <Step title="Create OAuth Consumer">
    Create a new OAuth Consumer in your Bitbucket instance to authenticate your
    users with Widgetbook Cloud.

    Go to **"Workspace Settings"** > **"OAuth consumers"** > **"Add consumer"**.
    Then fill the form with the following information and click **"Save"**.

    | Field           | Value                                |
    | --------------- | ------------------------------------ |
    | Name            | Widgetbook |
    | Callback URL    | `https://clerk.widgetbook.io/v1/oauth_callback` |
    | URL             | `https://www.widgetbook.io/` |
    | Permissions (5) | `Account (Email, Read)` - `Repositories (Read)` - `Pull Requests (Read)` - `Webhooks (Read and Write)` |

  </Step>
  <Step title="Send us the Information">
    Send the following information to [engineering@widgetbook.io](mailto:engineering@widgetbook.io):

    <Warning>
      (*) Do not send the **Consumer Secret** as plain text as it is **sensitive information**.
      Instead, use a one-time shared link from your password or secret manager service (e.g. [1Password](https://support.1password.com/share-items/))
    </Warning>

    | Item                   | Reason                |
    | ---------------------- | --------------------- |
    | Company Name (e.g. Acme) | Displayed in the user profile page for integration. |
    | Company Logo (1:1 PNG) | Displayed in the user profile page for integration. |
    | Company Email Domain (e.g. acme.com) | Users who have a Widgetbook Cloud account with an email having this domain, will be able to see the integration is the user profile page. |
    | Company Bitbucket Domain (e.g. bitbucket.acme.com) | To send Bitbucket API requests to. |
    | Consumer Key (from previous step) | For OAuth Integration |
    | (*) Consumer Secret (from previous step) | For OAuth Integration |

  </Step>
</Steps>
```

### Widgetbook Cloud x Bitbucket

Source: https://docs.widgetbook.io/cloud/guides/bitbucket/upload

```mdx
# Widgetbook Cloud x Bitbucket

If you want to use Widgetbook Cloud with your existing Bitbucket repository, here's a step-by-step guide to help you get started.

<Steps>
  <Step title="Setup Widgetbook Cloud Project">
    Create a new project in Widgetbook Cloud by importing your Bitbucket repository.
    You might need to connect your Bitbucket account to Widgetbook Cloud to access your repositories.
  </Step>
  
  <Step title="Add API Key as a Variable">
    Add `WIDGETBOOK_API_KEY` to your [Bitbucket's Pipeline Variables](https://support.atlassian.com/bitbucket-cloud/docs/variables-and-secrets/#User-defined-variables).
  </Step>

  <Step title="Create Bitbucket Pipeline">
    To upload a [Widgetbook Build](/cloud/builds/overview) for each new commit in your repository, you need to setup up a pipeline in Bitbucket that runs on every push.

    Create a new Bitbucket pipeline file in your repository under `bitbucket-pipelines.yml` with the following content:

    ```yaml
    image: ghcr.io/cirruslabs/flutter

    pipelines:
      default:
        - step:
            name: Build Widgetbook
            script:
              # Bootstrap App
              - flutter pub get
              - flutter gen-l10n
              # Bootstrap Widgetbook
              - cd widgetbook
              - flutter pub get
              - dart run build_runner build -d
              - flutter build web
            artifacts:
              - widgetbook/build/web/**
              - widgetbook/.dart_tool/build/generated/**

        - step:
            name: Deploy Widgetbook
            script:
              - dart pub global activate widgetbook_cli
              - export PATH="$PATH":"$HOME/.pub-cache/bin"
              - cd widgetbook
              - widgetbook cloud build push --api-key "$WIDGETBOOK_API_KEY"
    ```

  </Step>
  
  <Step title="Create a Merge Request">
    To test out if everything works properly, create a new branch and push a commit to it, then submit a PR with the new branch.
    After the build upload finishes, a commit status will be added to your PR once the [Widgetbook Review](/cloud/reviews) is ready.
  </Step>
</Steps>

## Troubleshooting

To make sure that your Widgetbook Cloud setup works properly, you can check that your repository has a "Widgetbook Cloud" Webhook configured _(under Repository Settings > Webhooks)_ to notify Widgetbook Cloud about new PRs.
```

### Widgetbook Cloud x Codemagic

Source: https://docs.widgetbook.io/cloud/guides/codemagic/upload

```mdx
# Widgetbook Cloud x Codemagic

Codemagic is just a CI/CD platform, which means it needs to be accompanied by a version control system (i.e. [GitHub](/cloud/guides/github/upload), [GitLab](/cloud/guides/gitlab/upload), [Azure](/cloud/guides/azure/upload) or [Bitbucket](/cloud/guides/bitbucket/upload)). In this guide, we will just be showing the workflow setup, but you need to check other version control system guides to make the [Widgetbook Cloud Reviews](/cloud/reviews) work.

<Steps>
  <Step title="Add API Key as a Secret">
    Add `WIDGETBOOK_API_KEY` under `widgetbook_credentials` group in your
    [Codemagic project's environment
    variables](https://docs.codemagic.io/yaml-basic-configuration/configuring-environment-variables/#configuring-environment-variables).
    You can find the API key in the Widgetbook Cloud's **project settings page**.
  </Step>

  <Step title="Setup Workflow">
    To upload a [Widgetbook Build](/cloud/builds/overview) for each new commit in your repository, add the following workflow to your Codemagic `codemagic.yaml` file

    ```yaml
    workflows:
      widgetbook-cloud-hosting:
        name: Widgetbook Cloud Hosting

        triggering:
          events:
            - push

        environment:
          flutter: stable
          groups:
            - widgetbook_credentials

        scripts:
          - name: Bootstrap App
            script: |
              flutter pub get
              # Add any other steps needed to make your
              # app widgets available for Widgetbook

          - name: Build Widgetbook
            working_directory: widgetbook
            script: |
              flutter pub get
              dart run build_runner build -d
              flutter build web -t lib/main.dart

          - name: Install Widgetbook CLI
            script: dart pub global activate widgetbook_cli

          - name: Push Widgetbook Build
            working_directory: widgetbook
            script: widgetbook cloud build push --api-key $WIDGETBOOK_API_KEY
    ```

  </Step>

</Steps>
```

### Access builds from GitHub commit statuses

Source: https://docs.widgetbook.io/cloud/guides/github/access-from-commit

```mdx
# Access builds from GitHub commit statuses

Widgetbook Cloud links builds to [GitHub commit statuses](https://docs.github.com/en/rest/commits/statuses#about-commit-statuses) so accessing Widgetbook builds integrates seamlessly with your GitHub workflow.

<Image src="/assets/cloud/builds/github-link.png" caption="Widgetbook builds are accessed via commit statuses" zoom />

## Prerequisites

Linking builds to GitHub commit statuses requires API access to your GitHub repository.
By [connecting a Widgetbook project to a GitHub repository](/cloud/projects/connect#connect-project) you allow Widgetbook Cloud to write commit statuses of your repository and add Widgetbook build links to commits.
Once you've connected your project, build linking via commit statuses is automatically enabled.

<Warning>
Commit statuses are only created for new builds after the project is connected to a GitHub repository.
If you've uploaded builds before connecting the project, commit statuses are not created for these builds.
</Warning>

## How it works

<Steps>
  <Step title="Upload a build">
    Upload a build as described in the [upload build guide](/cloud/guides/github/upload).
  </Step>
  <Step title="Navigate to your repository">
    While the build processes or after the build finished successfully, navigate to your GitHub repository.

    <Image src="/assets/guides/github/github-repository.png" />
  </Step>
  <Step title="Locate the commit status">
    Locate the commit status indicator for the commit.
    The commit status indicator is a green checkmark, a yellow dot, or a red cross next to the commit message.

    <Image src="/assets/guides/github/commit-status.png" />
  </Step>
  <Step title="Build commit status">
    Locate the commit status called _Widgetbook Build_ in the checks list and press the Details button.

    <Image src="/assets/guides/github/check-list.png" />
  </Step>
  <Step title="Inspect the build">
    Once you clicked the Details button, you are redirected to the build details page in Widgetbook Cloud.

    <Image src="/assets/guides/github/build-detail.png" />
  </Step>
</Steps>
```

### cloud/guides/github/enforce-reviews

Source: https://docs.widgetbook.io/cloud/guides/github/enforce-reviews

```mdx
## Enforcing reviews

Depending on your goals and workflow, you may want to enforce reviews for all changes in your project, requiring an accepted Widgetbook review for each pull request with changes.

When Widgetbook Cloud reviews are enabled for your project, Widgetbook Cloud automatically creates a review for your PR and posts the URL in your GitHub PR as a commit status. 
If changes are requested, the commit status reflects the Widgetbook review status on your PR.
However, GitHub's default configuration allows PRs to be merged even if status checks are not passing or still pending.

<Image 
  caption="Example of a pending review not blocking the merge of the PR." 
  src="/assets/cloud/setup/github/unblocked-review.png" 
/>

To enforce reviews for all changes in your project, you can set up a branch protection rule in your GitHub repository. 
This prevents PRs from being merged until the Widgetbook review is accepted.

<Steps>
  <Step title="Open the rulesets settings page">
    Navigate to the repository settings in GitHub.
    In the navigation, click on *Rules* to open the accordion. 
    Then click on *Rulesets* to open the rulesets page.

    <Image src="/assets/cloud/setup/github/rulesets-page.png" />
  </Step>
  <Step title="Create a new ruleset">
    Click on *New ruleset* and create a new branch ruleset.
  </Step>
  <Step title="Set name">
    Set a name for the new ruleset. 
    For instance, set it to `Widgetbook Review`.
  </Step>
  <Step title="Activate rule">
    Set the enforcement status to `Active`.

    <Image src="/assets/cloud/setup/github/enable-ruleset.png" />
  </Step>
  <Step title="Create new target">
    Scroll down to the *Targets* section and click on *Create target*.
    Select the branch you want to protect.
    We recommend choosing *Include default branch*, but depending on your workflow you may want to choose one or multiple branches.

    <Image src="/assets/cloud/setup/github/create-new-target.png" />
  </Step>
  <Step title="Require status checks to pass">
    Scroll down to the rules section.
    Disable the pre-selected rules. 
    Once everything is deselected, enable the rule *Require status checks to pass*. 

    <Image src="/assets/cloud/setup/github/enable-status-checks.png" />
  </Step>
  <Step title="Add Widgetbook status check">
    To make the Widgetbook Review commit status required, click the *Add checks* button.
    In the search box, copy the name of the Widgetbook review status check, which is formatted as `Widgetbook Review (<widgetbook-project-name>)`.
    If you already have a pull request with a Widgetbook Review commit status, you can also obtain the name from the status checks section in the PR.

    <Image src="/assets/cloud/setup/github/review-commit-status.png" />

    In the example above, the status check is named `Widgetbook Review (widgetbook-explained)`.

    In the search box, select the item that is shown.
    <Image src="/assets/cloud/setup/github/create-new-check.png" />

    Once the dropdown menu is deselected, the check will be added.
  </Step>
  <Step title="Add a check source">
    If no source is configured, select the *Widgetbook* app as the source.

    <Image src="/assets/cloud/setup/github/edit-check-source.png" />
  </Step>
  <Step title="Create the branch ruleset">
    At the bottom of the page, click the *Create* button. 
  </Step>
  <Step title="Verify ruleset">
    Navigate to a PR to verify that the Widgetbook Review status check is now required to merge the PR.

    <Image src="/assets/cloud/setup/github/result.png" />
  </Step>
</Steps>
```

### Upload builds with GitHub Actions

Source: https://docs.widgetbook.io/cloud/guides/github/upload

```mdx
# Upload builds with GitHub Actions 

If you want to use Widgetbook Cloud with your existing GitHub repository, here's a step-by-step guide to help you get started.

<Steps>
  <Step title="Setup Widgetbook Cloud Project">
    Create a new project in Widgetbook Cloud by importing your GitHub repository.
    You might need to connect your GitHub account to Widgetbook Cloud to access your repositories.
  </Step>
  
  <Step title="Add API Key as a Secret">
    Add `WIDGETBOOK_API_KEY` to your [GitHub repository's secrets](https://docs.github.com/en/actions/security-guides/using-secrets-in-github-actions#creating-secrets-for-a-repository). You can find the API key in the Widgetbook Cloud's **project settings page**.
  </Step>

  <Step title="Create GitHub Actions Workflow">
    To upload a [Widgetbook Build](/cloud/builds/overview) for each new commit in your repository, you need to setup up a workflow in GitHub Actions that runs on every push.

    ```yaml
    name: Widgetbook Cloud Hosting
    on: push

    jobs:
      widgetbook-cloud-hosting:
        runs-on: ubuntu-latest
        steps:
          - name: Checkout
            uses: actions/checkout@v3

          - name: Setup flutter
            uses: subosito/flutter-action@v2
            with:
                channel: stable

          - name: Bootstrap App
            run: |
                flutter pub get
                # Add any other steps needed to make your
                # app widgets available for Widgetbook

          - name: Build Widgetbook
            working-directory: widgetbook
            run: |
                flutter pub get
                dart run build_runner build -d
                flutter build web -t lib/main.dart

          - name: Install Widgetbook CLI
            run: dart pub global activate widgetbook_cli

          - name: Push Widgetbook Build
            working-directory: widgetbook
            run: widgetbook cloud build push --api-key ${{ secrets.WIDGETBOOK_API_KEY }}
    ```

  </Step>

  <Step title="Create a Pull Request">
    To test out if everything works properly, create a new branch and push a commit to it, then submit a PR with the new branch.
    After the build upload finishes, a commit status will be added to your PR once the [Widgetbook Review](/cloud/reviews) is ready.
  </Step>

</Steps>
```

### Access builds from GitLab commit statuses

Source: https://docs.widgetbook.io/cloud/guides/gitlab/access-from-commit

```mdx
# Access builds from GitLab commit statuses

Widgetbook Cloud links builds to [GitLab commit statuses](https://docs.gitlab.com/api/commits/#commit-status) so accessing Widgetbook builds integrates seamlessly with your GitLab workflow.

## Prerequisites

Linking builds to GitLab commit statuses requires API access to your GitLab repository.
By [connecting a Widgetbook project to a GitLab repository](/cloud/projects/connect#connect-project) you allow Widgetbook Cloud to write commit statuses of your repository and add Widgetbook build links to commits.
Once you've connected your project, build linking via commit statuses is automatically enabled.

<Warning>
Commit statuses are only created for new builds after the project is connected to a GitLab repository.
If you've uploaded builds before connecting the project, commit statuses are not created for these builds.
</Warning>

## How it works

Widgetbook Cloud adds a commit status to the commit of the uploaded build.
The commit status is a link to the build details page in Widgetbook Cloud and is accessed by clicking the "Widgetbook Build" stage in the GitLab pipelines view.
```

### Widgetbook Cloud x Self-managed GitLab

Source: https://docs.widgetbook.io/cloud/guides/gitlab/self-managed

```mdx
# Widgetbook Cloud x Self-managed GitLab

<Info>
  This guide works for both **GitLab self-managed** and **GitLab Dedicated**.
</Info>

If you want to use Widgetbook Cloud with your self-managed GitLab instance (e.g. `gitlab.acme.dev`), here's a pre-requisite guide to help you get started, then you can follow our normal [Gitlab setup guide](/cloud/guides/gitlab/upload).

<Steps>
  <Step title="Create OAuth Application">
    Create a new OAuth Application in your GitLab instance to authenticate your
    users with Widgetbook Cloud.

    Go to **"Group Settings"** > **"Applications"** > **"Add new application"**.
    Then fill the form with the following information and click **"Save application"**.

    | Field          | Value                                |
    | -------------- | ------------------------------------ |
    | Name           | Widgetbook                           |
    | Redirect URI   | `https://clerk.widgetbook.io/v1/oauth_callback` |
    | Confidential   | ✅                                   |
    | Scopes         | `api` - `read_user` - `openid` - `profile` - `email` |

  </Step>
  <Step title="Send us the Information">
    Send the following information to [engineering@widgetbook.io](mailto:engineering@widgetbook.io):

    <Warning>
      (*) Do not send the **Application Secret** as plain text as it is **sensitive information**.
      Instead, use a one-time shared link from your password or secret manager service (e.g. [1Password](https://support.1password.com/share-items/))
    </Warning>

    | Item                   | Reason                |
    | ---------------------- | --------------------- |
    | Company Name (e.g. Acme) | Displayed in the user profile page for integration. |
    | Company Logo (1:1 PNG) | Displayed in the user profile page for integration. |
    | Company Email Domain (e.g. acme.com) | Users who have a Widgetbook Cloud account with an email having this domain, will be able to see the integration is the user profile page. |
    | Company GitLab Domain (e.g. gitlab.acme.com) | To send GitLab API requests to. |
    | Application (from previous step) | For OAuth Integration |
    | (*) Application Secret (from previous step) | For OAuth Integration |

  </Step>
</Steps>
```

### Widgetbook Cloud x GitLab

Source: https://docs.widgetbook.io/cloud/guides/gitlab/upload

```mdx
# Widgetbook Cloud x GitLab

If you want to use Widgetbook Cloud with your existing GitLab repository, here's a step-by-step guide to help you get started.

<Steps>
  <Step title="Setup Widgetbook Cloud Project">
    Create a new project in Widgetbook Cloud by importing your GitLab repository.
    You might need to connect your GitLab account to Widgetbook Cloud to access your repositories.
  </Step>
  
  <Step title="Add API Key as a Variable">
    Add `WIDGETBOOK_API_KEY` to your [GitLab's CI/CD Variables](https://docs.gitlab.com/ee/ci/variables/#for-a-project). You can find the API key in the Widgetbook Cloud's **project settings page**. Make sure you **uncheck "Protect variable" checkbox**, to allow the variable to be accessible from feature branches.
  </Step>

  <Step title="Create GitLab CI/CD Pipeline">
    To upload a [Widgetbook Build](/cloud/builds/overview) for each new commit in your repository, you need to setup up a pipeline in GitLab CI/CD that runs on every push.

    Create a new GitLab [branch pipeline](https://docs.gitlab.com/ci/pipelines/pipeline_types/#branch-pipeline) file in your repository under `.gitlab-ci.yml` with the following content:

    <Warning>
      Make sure that you are using a branch pipeline (i.e. no `if: $CI_PIPELINE_SOURCE == 'merge_request_event'` rule), and not a merge request pipeline.

      Widgetbook builds should be uploaded for all branches to ensure a build is always available from the base branch (e.g., `main`).
    </Warning>

    ```yaml
    image: ghcr.io/cirruslabs/flutter

    stages:
      - widgetbook

    widgetbook_cloud_hosting:
      stage: widgetbook
      script:
        # Bootstrap App
        - flutter pub get
        - flutter gen-l10n
        # Bootstrap Widgetbook
        - cd widgetbook
        - flutter pub get
        - dart run build_runner build -d
        - flutter build web
        # Upload to Widgetbook Cloud
        - dart pub global activate widgetbook_cli
        - export PATH="$PATH":"$HOME/.pub-cache/bin"
        - widgetbook cloud build push --api-key "$WIDGETBOOK_API_KEY"
    ```

  </Step>
  
  <Step title="Create a Merge Request">
    To test out if everything works properly, create a new branch and push a commit to it, then submit a MR with the new branch.
    After the build upload finishes, a commit status will be added to your MR once the [Widgetbook Review](/cloud/reviews) is ready.
  </Step>
</Steps>

## Troubleshooting

To make sure that your Widgetbook Cloud setup works properly, you can check that your repository has a Webhook configured _(under Project Settings > Webhooks)_ to notify Widgetbook Cloud about new MRs.

## GitLab Merged Result Pipelines

If you are using [GitLab Merged Result Pipelines](https://docs.gitlab.com/ee/ci/pipelines/merged_results_pipelines.html), that means your pipeline runs on a commit that is not in your MR branch (i.e. the merged result commit). In this case, the commit status from Widgetbook Cloud needs to be added to that "merged result" commit, to be visible in the MR, and block the merge if the Widgetbook Review fails.

<Warning>
  Since your builds will be uploaded from a **merged result commit**, that means
  you need to keep your MR's **base build** up-to-date, by merging the base
  branch into your MR branch _(rebasing also works)_ before you review the
  changes on Widgetbook Cloud.
</Warning>

To achieve this, replace the `cloud build push` command as follows:

```yaml
widgetbook:
  script:
    - # Previous scripts...
    - widgetbook cloud build push --api-key "$WIDGETBOOK_API_KEY" // [!code --]
    - | // [!code ++]
      if [ "$CI_PIPELINE_SOURCE" == "merge_request_event" ]; then // [!code ++]
        # Upload a merged result build // [!code ++]
        widgetbook cloud build push \ // [!code ++]
          --api-key "$WIDGETBOOK_API_KEY" \ // [!code ++]
          --commit "$CI_MERGE_REQUEST_SOURCE_BRANCH_SHA" \ // [!code ++]
          --branch "$CI_MERGE_REQUEST_SOURCE_BRANCH_NAME" \ // [!code ++]
          --merged-result-commit "$CI_COMMIT_SHA" // [!code ++]
      else // [!code ++]
        # Upload a regular build // [!code ++]
        widgetbook cloud build push --api-key "$WIDGETBOOK_API_KEY"  // [!code ++]
      fi // [!code ++]
```
```

### Email Notifications

Source: https://docs.widgetbook.io/cloud/reviews/notifications/email

```mdx
# Email Notifications

Widgetbook Cloud sends email notifications to keep reviewers informed about pull request assignments and review status changes.

## What you'll receive

Email notifications are sent in two events:

### Reviewer assignment

When a user is assigned as a reviewer to a pull request, they receive an email immediately with:

- Pull request title
- Direct link to view the pull request in Widgetbook Cloud

This email is always sent when reviewers are assigned.

### Review status updates

When a pull request's review status changes, all assigned reviewers receive an email with:

- Project name
- Pull request title
- Current review status (pending, approved, or changes requested)
- Direct link to view the pull request

<Info>
  For "Pending" status: If reviewers have already been notified and no new reviews have been submitted, duplicate notifications won't be sent to avoid email spam.
</Info>

## Enable review status emails

Review status update emails must be enabled in your project settings.

<Steps>
  <Step title="Open project settings">
    Navigate to your project on Widgetbook Cloud and click on the "Settings" tab.

    Select the "General" section from the left sidebar.
  </Step>
  <Step title="Enable the feature">
    Locate the "Enable pull request review emails" checkbox and enable it.
  </Step>
  <Step title="Save changes">
    Click the "Save Changes" button at the bottom of the page to apply your settings.
  </Step>
</Steps>

<Info>
  Review status emails are only sent to assigned reviewers on the pull request.
  If no reviewers are assigned, no emails are sent.
</Info>
```

### Slack Notifications

Source: https://docs.widgetbook.io/cloud/reviews/notifications/slack

```mdx
# Slack Notifications

Widgetbook Cloud integrates with Slack to keep your team informed about visual pull requests that need review. 
Notifications are sent automatically to your Slack workspace, ensuring that reviews don't go unnoticed.

## What you'll receive

Slack notifications are sent when a visual pull request requires review. Each notification includes:

- Pull request number and title
- Direct link to view changes in Widgetbook Cloud
- Workspace and project name

Notifications appear as Slack messages with a yellow accent to draw attention.

<Info>
  Notifications are only sent for pull requests with "pending review" status. 
  Approved pull requests or those with "changes requested" do not trigger notifications.
</Info>

## Setup Slack integration

<Info>
  You need admin access to your Widgetbook Cloud workspace and permission to install apps in your Slack workspace.
</Info>

<Steps>
  <Step title="Open workspace settings">
    Navigate to your Widgetbook Cloud workspace and open the General Settings page.
  </Step>
  <Step title="Connect to Slack">
    Find the Slack integration section and click "Connect to Slack".
    
    You'll be redirected to Slack's authorization page.
  </Step>
  <Step title="Authorize Widgetbook">
    Select the Slack workspace you want to connect and grant Widgetbook permission to post messages via incoming webhooks.
    
    You'll be redirected back to Widgetbook Cloud once authorization is complete.
  </Step>
  <Step title="Verify configuration">
    Confirm that the Slack integration shows as connected in your workspace settings.
    
    The integration is now active for all projects in your workspace.
  </Step>
</Steps>

## Configuration scope

The Slack integration is configured at the workspace level:

- Set it up once per Widgetbook workspace
- All projects within the workspace send notifications to the configured Slack channel
- No per-project configuration needed

## Manage notifications

To stop receiving Slack notifications, go to your workspace General Settings and disconnect the Slack integration. This will disable all notifications for that workspace.
```
