# 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]
      ],
    );
  }
}
```
