Interactive demo of slider field variants
/**
* Slider Field Demo - Interactive examples of slider variants
*/
import { Form, FormBuilder } from '@saastro/forms';
import { FormProvider } from '@/components/FormProvider';
import { TooltipProvider } from '@/components/ui/tooltip';
const config = FormBuilder.create('slider-demo')
.layout('manual')
.columns(12)
.addField('volume', (f) =>
f
.type('slider')
.label('Volume')
.helperText('Adjust the volume level')
.range(0, 100, 1)
.value([75])
.showValue(true)
.showLabels(true)
.valueFormat('{value}%')
.required()
.columns({ default: 12 }),
)
.addField('priceRange', (f) =>
f
.type('slider')
.label('Price Range')
.helperText('Select minimum and maximum price')
.range(0, 1000, 10)
.sliderVariant('range')
.value([200, 800])
.showValue(true)
.showLabels(true)
.valueFormat('${value}')
.required()
.columns({ default: 12 }),
)
.addField('rating', (f) =>
f
.type('slider')
.label('Rating')
.helperText('Rate from 1 to 5 stars')
.range(1, 5, 1)
.value([3])
.showValue(true)
.showLabels(true)
.valueFormat('{value} ★')
.required()
.columns({ default: 12, md: 6 }),
)
.addField('temperature', (f) =>
f
.type('slider')
.label('Temperature')
.helperText('Set target temperature')
.range(-10, 40, 1)
.value([22])
.showValue(true)
.showLabels(true)
.valueFormat('{value}°C')
.required()
.columns({ default: 12, md: 6 }),
)
.addStep('main', ['volume', 'priceRange', 'rating', 'temperature'])
.build();
export default function SliderDemo() {
const handleSubmit = (data: Record<string, unknown>) => {
console.log('Form submitted:', data);
alert('Form submitted! Check console for data.');
};
return (
<TooltipProvider>
<FormProvider>
<Form config={config} onSubmit={handleSubmit} className="space-y-6" />
</FormProvider>
</TooltipProvider>
);
} Overview
The slider field provides a visual way to select numeric values. It supports three variants:
default- Single thumb for selecting one valuerange- Two thumbs for selecting a min/max rangemulti- 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[]:
defaultvariant → one element, e.g.[50]rangevariant → two elements[from, to], e.g.[200, 800]multivariant →thumbCountelements, 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)
import { FormBuilder } from '@saastro/forms';
import { z } from 'zod';
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
.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
.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
.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
.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[]:
import { z } from 'zod';
.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 compileslider/rangefields to az.number()schema, which can never accept the slider’snumber[]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
.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
.addField('volume', (f) =>
f.type('slider')
.label('Volume')
.range(0, 100, 1)
.schema(z.array(z.number()))
.columns({ default: 12, md: 6 }),
)
JSON Styling
{
"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
Slidercomponent you inject (see Components); the typical shadcn/ui 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