# Field: Slider (category: special)

URL: https://docs.forms.saastro.io/docs/slider

<FieldPreview name="slider" description="Interactive demo of slider field variants" />

## Overview

The slider field provides a visual way to select numeric values. It supports three variants:

- `default` - Single thumb for selecting one value
- `range` - Two thumbs for selecting a min/max range
- `multi` - Multiple thumbs for selecting multiple values

The `range` field **type** is an alias of `slider` — identical props and identical rendering. Don't confuse it with the `range` **variant**, which gives a single slider field two thumbs.

## Value

The slider always stores its value as a **`number[]`**:

- `default` variant → one element, e.g. `[50]`
- `range` variant → two elements `[from, to]`, e.g. `[200, 800]`
- `multi` variant → `thumbCount` elements, e.g. `[20, 40, 60, 80]`

When no value is set, the slider _displays_ a computed default — the midpoint for `default`, the 25% and 75% positions for `range`, and evenly distributed thumbs for `multi` (or `defaultValue` if provided). However, nothing is written to the form state until the user moves a thumb. Set `value` explicitly if your submit handler or validation schema expects a value without user interaction.

## Usage

### Single Value (Default)

```tsx

const config = FormBuilder.create('settings')
  .addField('volume', (f) =>
    f
      .type('slider')
      .label('Volume')
      .range(0, 100, 1)
      .value([50])
      .valueFormat('{value}%')
      .schema(z.array(z.number().min(0).max(100))),
  )
  .addStep('main', ['volume'])
  .build();
```

### Range Selection

```tsx
.addField('priceRange', (f) =>
  f.type('slider')
    .label('Price Range')
    .range(0, 1000, 10)
    .sliderVariant('range')
    .value([200, 800])
    .valueFormat('${value}')
    .schema(z.array(z.number().min(0).max(1000)).length(2)),
)
```

### Multi-Thumb

```tsx
.addField('breakpoints', (f) =>
  f.type('slider')
    .label('Breakpoints')
    .range(0, 100, 5)
    .sliderVariant('multi')
    .thumbCount(4)
    .value([20, 40, 60, 80])
    .schema(z.array(z.number()).length(4)),
)
```

### Vertical Orientation

```tsx
.addField('temperature', (f) =>
  f.type('slider')
    .label('Temperature')
    .range(-20, 40, 1)
    .sliderOrientation('vertical')
    .value([22])
    .valueFormat('{value}°C')
    .schema(z.array(z.number().min(-20).max(40))),
)
```

## Props

| Prop           | Type                                                  | Default        | Description                                              |
| -------------- | ----------------------------------------------------- | -------------- | -------------------------------------------------------- |
| `type`         | `'slider' \| 'range'`                                 | -              | Field type (required); `'range'` is an alias of `'slider'` |
| `min`          | `number`                                              | `0`            | Minimum value                                            |
| `max`          | `number`                                              | `100`          | Maximum value                                            |
| `step`         | `number`                                              | `1`            | Step increment                                           |
| `variant`      | `'default' \| 'range' \| 'multi'`                     | `'default'`    | Slider variant                                           |
| `orientation`  | `'horizontal' \| 'vertical'`                          | `'horizontal'` | Slider orientation                                       |
| `thumbCount`   | `number`                                              | `3`            | Number of thumbs for 'multi' variant                     |
| `showLabels`   | `boolean`                                             | `true`         | Show min/max labels                                      |
| `showValue`    | `boolean`                                             | `true`         | Show current value(s) next to the label                  |
| `valueFormat`  | `string`                                              | -              | Format string (use `{value}` placeholder)                |
| `defaultValue` | `number[]`                                            | -              | Initial thumb positions shown before interaction (display only — not written to the form state) |
| `columns`      | `Partial<Record<Breakpoint, number>>`                 | -              | Grid columns by breakpoint                               |
| `hidden`       | `boolean \| function \| ConditionGroup \| Responsive` | `false`        | Hide the field                                           |
| `disabled`     | `boolean \| function \| ConditionGroup`               | `false`        | Disable the slider                                       |

In the builder API, `variant` and `orientation` are set with `.sliderVariant()` and `.sliderOrientation()`.

## Value Format Examples

```tsx
.valueFormat('${value}')    // Shows "$50"
.valueFormat('{value}%')    // Shows "75%"
.valueFormat('{value}°C')   // Shows "22°C"
.valueFormat('{value} pts') // Shows "100 pts"
```

For the `range` variant the two values are shown as `"$200 - $800"`; for `multi`, values are comma-joined.

## Validation

Always validate sliders with a Zod **array** schema via `.schema()` — the stored value is `number[]`:

```tsx

.addField('rating', (f) =>
  f.type('slider')
    .label('Rating')
    .range(1, 5, 1)
    .value([3])
    .schema(z.array(z.number().min(1).max(5)).length(1)),
)
```

> **Known limitation:** the declarative validation rules (`.required()`, `.numberRange()`) currently compile `slider`/`range` fields to a `z.number()` schema, which can never accept the slider's `number[]` value — a form using them on a slider will always fail validation. Use a Zod array schema as shown above.

Because the slider only writes to the form state when the user moves a thumb, set an initial `value` (as in the example) if the schema must pass without interaction.

## Styling

### Custom Classes

```tsx
.addField('volume', (f) =>
  f.type('slider')
    .label('Volume')
    .range(0, 100, 1)
    .schema(z.array(z.number()))
    .classNames({
      wrapper: 'bg-muted/30 p-4 rounded-lg',
      input: 'accent-primary',
      label: 'text-lg font-medium',
      helper: 'text-xs text-muted-foreground',
    }),
)
```

### Responsive Layout

```tsx
.addField('volume', (f) =>
  f.type('slider')
    .label('Volume')
    .range(0, 100, 1)
    .schema(z.array(z.number()))
    .columns({ default: 12, md: 6 }),
)
```

### JSON Styling

```json
{
  "type": "slider",
  "label": "Volume",
  "min": 0,
  "max": 100,
  "wrapper_className": "bg-muted/30 p-4 rounded-lg",
  "label_className": "text-lg font-medium",
  "columns": { "default": 12, "md": 6 }
}
```

## Accessibility

- Built on the `Slider` component you inject (see [Components](/docs/components)); the typical [shadcn/ui](https://ui.shadcn.com) Slider is Radix-based and fully accessible
- Supports keyboard navigation (arrow keys)
- Announces value changes to screen readers
- Focus visible states
- Step increments work with keyboard navigation
