Slider
A native range input with a hard-edged track and thumb, for volume, seeking, and any value on a scale.
stableSlider.tsx
Examples
With a label and readout
60%
slider/basic.tsx
"use client";
import { useState } from "react";
import { Slider } from "@streamuilib/react";
export default function SliderBasic() {
const [value, setValue] = useState(60);
return (
<div className="w-full max-w-xs">
<div className="mb-2 flex justify-between">
<label htmlFor="slider-brightness" className="font-bold">
Brightness
</label>
<span className="font-mono text-sm tabular-nums">{value}%</span>
</div>
<Slider id="slider-brightness" value={value} onValueChange={setValue} aria-valuetext={`${value}%`} />
</div>
);
}Tones
slider/tones.tsx
"use client";
import { useState } from "react";
import { Slider, type Tone } from "@streamuilib/react";
const tones: Tone[] = ["lime", "pink", "blue", "violet", "orange"];
export default function SliderTones() {
const [values, setValues] = useState([20, 40, 60, 80, 50]);
return (
<div className="flex w-full max-w-xs flex-col gap-3">
{tones.map((tone, i) => (
<Slider
key={tone}
aria-label={`${tone} level`}
tone={tone}
value={values[i]}
onValueChange={(v) => setValues((all) => all.map((old, j) => (j === i ? v : old)))}
/>
))}
</div>
);
}Installation
bun add @streamuilib/reactusage.tsx
import { Slider } from "@streamuilib/react";
<Slider aria-label="Volume" value={volume} onValueChange={setVolume} />API
| Prop | Type | Default | Description |
|---|---|---|---|
| value | number | — | Current value. Required; the slider is always controlled. |
| onValueChange | (value: number) => void | — | Called with the new value as a number. |
| min / max | number | 0 / 100 | Range of the slider. |
| step | number | "any" | 1 | Increment; any allows continuous values. |
| tone | "lime" | "pink" | "blue" | "violet" | "orange" | "lime" | Color of the filled part. |
| aria-valuetext | string | — | What screen readers announce, e.g. "1:23 of 3:05". |
| ...props | ComponentProps<"input"> | — | Native input attributes, e.g. disabled or id. |
Accessibility
- A real <input type="range">: arrow keys, Page Up/Down, Home/End, and touch all work.
- Give it a name with a <label> or aria-label, and use aria-valuetext when the raw number isn't meaningful.
- The focus ring sits around the whole track, and the thumb fills with the tone while dragging.
Source
packages/react/src/Slider.tsx
"use client";
import type { ChangeEvent, ComponentProps, CSSProperties } from "react";
import { cn } from "./cn";
import type { Tone } from "./tones";
export type SliderProps = Omit<ComponentProps<"input">, "type" | "value" | "defaultValue" | "onChange"> & {
value: number;
min?: number;
max?: number;
step?: number | "any";
/** Called with the new numeric value. */
onValueChange?: (value: number) => void;
onChange?: (event: ChangeEvent<HTMLInputElement>) => void;
/** Color of the filled part of the track. */
tone?: Tone;
};
/**
* A native range input with a brutal track and thumb (styles in theme.css).
* Native means arrow keys, Page Up/Down, Home/End, touch, and screen
* reader support for free. Pass `aria-valuetext` for human-readable values
* like "1:23 of 3:05".
*/
export function Slider({
value,
min = 0,
max = 100,
step = 1,
onValueChange,
onChange,
tone = "lime",
className,
style,
...props
}: SliderProps) {
const span = max - min;
const fill = span > 0 ? ((Math.min(max, Math.max(min, value)) - min) / span) * 100 : 0;
return (
<input
type="range"
value={value}
min={min}
max={max}
step={step}
onChange={(e) => {
onChange?.(e);
onValueChange?.(Number(e.target.value));
}}
className={cn("sui-range w-full", className)}
style={{ ...style, "--sui-fill": `${fill}%`, "--sui-range-color": `var(--${tone})` } as CSSProperties}
{...props}
/>
);
}