# 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: (_) {},
  );
}
```
