Why Use It
- Best for volume, brightness, balance, and other single-value controls.
- Keeps the interaction model of Radix Slider while exposing a simpler single-number API.
- Works well inside compact settings rows, control racks, and form-driven preference panels.
- Supports signed ranges, so the same component can handle centering and left or right offsets.
Installation
Install the slider directly, then layer your labels and readouts around the component.
$ bunx --bun shadcn@latest add https://sabraman.ru/r/legacy-slider.jsonQuick Start
Start with one labeled slider and a value readout. That pattern covers most settings surfaces.
"use client";
import { useState } from "react";
import { LegacySlider } from "@/components/legacy-slider";
export function LegacySliderSingleExample() {
const [volume, setVolume] = useState(58);
return (
<div className="flex w-full max-w-[320px] flex-col gap-3">
<div className="flex items-center justify-between font-medium text-[#98a6bf] text-sm">
<span>Media Volume</span>
<span>{Math.round(volume)}%</span>
</div>
<LegacySlider
aria-label="Media volume"
onValueChange={setVolume}
value={volume}
/>
</div>
);
}Examples
Single Slider
A basic single slider with a value label for the default settings pattern.
"use client";
import { useState } from "react";
import { LegacySlider } from "@/components/legacy-slider";
export function LegacySliderSingleExample() {
const [volume, setVolume] = useState(58);
return (
<div className="flex w-full max-w-[320px] flex-col gap-3">
<div className="flex items-center justify-between font-medium text-[#98a6bf] text-sm">
<span>Media Volume</span>
<span>{Math.round(volume)}%</span>
</div>
<LegacySlider
aria-label="Media volume"
onValueChange={setVolume}
value={volume}
/>
</div>
);
}Control Rack
Group several sliders into a control rack for audio or device settings.
"use client";
import { useState } from "react";
import { LegacySlider } from "@/components/legacy-slider";
function formatBalance(value: number) {
const roundedValue = Math.round(value);
if (roundedValue === 0) {
return "Center";
}
if (roundedValue < 0) {
return `L ${Math.abs(roundedValue)}`;
}
return `R ${roundedValue}`;
}
export function LegacySliderUsageExample() {
const [ringer, setRinger] = useState(68);
const [brightness, setBrightness] = useState(44);
const [balance, setBalance] = useState(12);
return (
<div className="flex w-full max-w-[340px] flex-col gap-4">
<div className="space-y-2">
<div className="flex items-center justify-between font-medium text-slate-500 text-sm">
<span>Ringer</span>
<span>{Math.round(ringer)}%</span>
</div>
<LegacySlider
aria-label="Ringer"
onValueChange={setRinger}
value={ringer}
/>
</div>
<div className="space-y-2">
<div className="flex items-center justify-between font-medium text-slate-500 text-sm">
<span>Brightness</span>
<span>{Math.round(brightness)}%</span>
</div>
<LegacySlider
aria-label="Brightness"
onValueChange={setBrightness}
value={brightness}
/>
</div>
<div className="space-y-2">
<div className="flex items-center justify-between font-medium text-slate-500 text-sm">
<span>Balance</span>
<span>{formatBalance(balance)}</span>
</div>
<LegacySlider
aria-label="Stereo balance"
max={50}
min={-50}
onValueChange={setBalance}
value={balance}
/>
</div>
</div>
);
}Signed Range
Use `min` and `max` to model a centered range such as stereo balance.
"use client";
import { useState } from "react";
import { LegacySlider } from "@/components/legacy-slider";
function formatBalance(value: number) {
if (value === 0) {
return "Center";
}
if (value < 0) {
return `Left ${Math.abs(value)}`;
}
return `Right ${value}`;
}
export function LegacySliderSignedRangeExample() {
const [balance, setBalance] = useState(12);
return (
<div className="flex w-full max-w-[320px] flex-col gap-3">
<div className="flex items-center justify-between font-medium text-[#98a6bf] text-sm">
<span>Stereo Balance</span>
<span>{formatBalance(balance)}</span>
</div>
<LegacySlider
aria-label="Stereo balance"
max={50}
min={-50}
onValueChange={setBalance}
value={balance}
/>
</div>
);
}Form-Controlled Slider
Wire the slider into form state when the value should submit with the rest of a settings form.
"use client";
import type { SubmitHandler } from "react-hook-form";
import { useForm } from "react-hook-form";
import { LegacyBarButton } from "@/components/legacy-bar-button";
import { showLegacyNotification } from "@/components/legacy-notification";
import { LegacySlider } from "@/components/legacy-slider";
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
} from "@/components/ui/form";
type SliderFormValues = {
brightness: number;
};
export function LegacySliderFormExample() {
const form = useForm<SliderFormValues>({
defaultValues: {
brightness: 64,
},
});
const onSubmit: SubmitHandler<SliderFormValues> = (values) => {
showLegacyNotification({
body: `Brightness set to ${Math.round(values.brightness)}%.`,
showIcon: false,
subtitle: "Display",
time: "now",
title: "Display Updated",
});
};
return (
<Form {...form}>
<form
className="flex w-full max-w-[320px] flex-col gap-5"
onSubmit={form.handleSubmit(onSubmit)}
>
<FormField
control={form.control}
name="brightness"
render={({ field }) => (
<FormItem className="space-y-3">
<div className="flex items-center justify-between">
<FormLabel className="font-medium text-[#98a6bf] text-sm">
Display Brightness
</FormLabel>
<span className="font-medium text-[#98a6bf] text-sm">
{Math.round(field.value)}%
</span>
</div>
<FormControl>
<LegacySlider
aria-label="Display brightness"
onValueChange={field.onChange}
value={field.value}
/>
</FormControl>
</FormItem>
)}
/>
<LegacyBarButton
className="w-full justify-center"
label="Save Brightness"
type="submit"
variant="accent"
/>
</form>
</Form>
);
}API Reference
LegacySliderProps wraps Radix Slider but replaces the array-based value API with a single number for value, defaultValue, onValueChange, and onValueCommit.
Also inherits: Omit< React.ComponentProps<typeof SliderPrimitive.Root>, "defaultValue" | "onValueChange" | "onValueCommit" | "value" >
| Prop | Type | Required |
|---|---|---|
defaultValue | number | No |
onValueChange | (value: number) => void | No |
onValueCommit | (value: number) => void | No |
value | number | No |
Notes
valueanddefaultValuetake a single number rather than the array shape from Radix.mindefaults to0andmaxdefaults to100, so signed ranges need both props set explicitly.- Use
onValueCommitwhen you only want to persist after the thumb is released.