React Slider Component Examples
Copy-paste React Slider examples: volume control, brightness label, vertical opacity dial, step snapping, sm/md/lg sizes, disabled state, and form submission.
A slider is the right control whenever the value is a continuous number on a known range — volume from zero to one hundred, brightness from off to full, opacity from transparent to opaque, a quality setting between low and high. The Slider component in Drivn is a single 120-line client file that reads pointer events off the track, snaps the value to the nearest step, and calls onChange with a plain number. No Radix dependency, no compound sub-components, no array values — one thumb, one number, one callback.
Every example on this page imports the component from @/components/ui/slider — the path the Drivn CLI installs it under — and pairs it with useState in a 'use client' page or panel. The Slider accepts value (controlled) or defaultValue (uncontrolled), min, max, step, orientation ('horizontal' | 'vertical'), size ('sm' | 'md' | 'lg'), disabled, and a name prop that wires it into form submission via a hidden range input. Every snippet is TypeScript because Drivn ships TypeScript-only.
The examples below cover a volume control with a live label, a brightness setting with a percent readout, a price filter, a vertical opacity dial, the three built-in size variants, step snapping, a disabled state, and a form-submission pattern that posts the value to a server action. The full props table sits on the Slider docs page, and the shadcn/ui comparison lives on Drivn vs shadcn Slider.
A volume control with a live label
The canonical Slider use is a single-thumb control with a number readout sitting just above or below. Hold volume in useState, pass it as value, pass setVolume as onChange, and let the component handle every drag, click, and snap on the track. The min={0} max={100} defaults are already what a percentage control wants, so this is the smallest possible setup.
The label updates inline because setVolume runs on every pointer move — the component's handlePointerDown resolver fires for both the initial click and every pointermove until pointerup. That means the readout tracks the thumb in real time without any throttling or debounce on the consumer side. Wrap the whole panel in a Card for a settings row, or drop it straight into a Popover for a header-bar volume toggle.
1 "use client" 2 3 import { useState } from "react" 4 import { Slider } from "@/components/ui/slider" 5 6 export default function VolumePanel() { 7 const [volume, setVolume] = useState(50) 8 9 return ( 10 <div className="w-full space-y-2"> 11 <div className="text-center text-sm font-medium"> 12 Volume {volume} 13 </div> 14 <Slider value={volume} onChange={setVolume} /> 15 <div className="flex justify-between text-xs text-muted-foreground"> 16 <span>0</span> 17 <span>100</span> 18 </div> 19 </div> 20 ) 21 }
A brightness setting with a percent readout
The same pattern as the volume control, dressed for a settings row: a Label on the left, the Slider in the middle, the percentage on the right. Use htmlFor on the label and a matching id on the Slider for the accessibility wiring. The wrapper is a flex row with items-center gap-4 so the three pieces line up vertically; the Slider stretches to fill the remaining space via flex-1.
The readout uses a fixed-width tabular-nums text node so the digits do not jitter when the value crosses single → double → triple digits. For a row of settings — brightness, contrast, saturation, hue — stack three or four of these inside a single Card. Each Slider keeps its own useState, or share a single object state if your settings save together.
1 "use client" 2 3 import { useState } from "react" 4 import { Label } from "@/components/ui/label" 5 import { Slider } from "@/components/ui/slider" 6 7 export default function BrightnessRow() { 8 const [brightness, setBrightness] = useState(75) 9 10 return ( 11 <div className="flex items-center gap-4"> 12 <Label htmlFor="brightness" className="w-24"> 13 Brightness 14 </Label> 15 <Slider 16 id="brightness" 17 value={brightness} 18 onChange={setBrightness} 19 className="flex-1" 20 /> 21 <span className="w-12 text-right text-sm tabular-nums text-muted-foreground"> 22 {brightness}% 23 </span> 24 </div> 25 ) 26 }
A vertical opacity dial
For a panel control that lives inside a toolbar or a layers sidebar, the vertical orientation reads better than a horizontal bar. Pass orientation="vertical" and the component swaps to the styles.vertical ruleset: the base height is h-48, the track runs the full height, the range fills from the bottom up, and the thumb sits at left-1/2 -translate-x-1/2. The percentage maps the same way — zero at the bottom, max at the top.
The vertical Slider is also where the vTrack width in the size mapping matters. size="sm" makes the track w-1, md (default) is w-1.5, lg is w-2. Pair the vertical Slider with a small numeric input above or below it for keyboard-only editing — Drivn's Slider does not bind arrow keys to step the value, so the input gives keyboard users a path. Drop the whole assembly into a fixed-width sidebar around 80 pixels wide.
1 "use client" 2 3 import { useState } from "react" 4 import { Slider } from "@/components/ui/slider" 5 6 export default function OpacityDial() { 7 const [opacity, setOpacity] = useState(70) 8 9 return ( 10 <div className="flex flex-col items-center gap-3"> 11 <span className="text-xs text-muted-foreground">Opacity</span> 12 <Slider 13 value={opacity} 14 onChange={setOpacity} 15 orientation="vertical" 16 size="md" 17 /> 18 <span className="text-sm font-medium tabular-nums">{opacity}%</span> 19 </div> 20 ) 21 }
Step snapping for quality presets
Set step={25} and the component snaps to 0 / 25 / 50 / 75 / 100. The snap helper inside the component runs Math.round((val - min) / step) * step + min on every pointer move, so the thumb visibly jumps between the discrete positions rather than gliding. This is the right pattern for quality presets ("low / medium / high / ultra"), star ratings on a 0–10 scale with a step of 1, or any input where the underlying value is enumerated and the user needs to feel the click.
The value you receive in onChange is always already snapped, so the rest of your code can treat the slider as a typed enum lookup: ["low", "medium", "high", "ultra"][quality / 25]. Combine step with min and max for non-percentage ranges — min={1} max={5} step={1} gives a five-position rating control, min={-50} max={50} step={5} gives an eleven-position centered scale.
1 "use client" 2 3 import { useState } from "react" 4 import { Slider } from "@/components/ui/slider" 5 6 const presets = ["low", "medium", "high", "ultra"] as const 7 8 export default function QualityPicker() { 9 const [quality, setQuality] = useState(50) 10 const preset = presets[quality / 25] ?? "ultra" 11 12 return ( 13 <div className="space-y-2"> 14 <div className="text-sm font-medium capitalize"> 15 Quality: {preset} 16 </div> 17 <Slider 18 value={quality} 19 onChange={setQuality} 20 step={25} 21 /> 22 </div> 23 ) 24 }
Three sizes side by side
The Slider ships with three built-in size tokens — sm, md, lg — that map directly to the styles.sizes object inside the component file. sm is h-1 track with a 12-pixel thumb, md is h-1.5 with a 16-pixel thumb (the default), lg is h-2 with a 20-pixel thumb. The vertical orientation uses the matching vTrack widths so the proportions stay consistent on both axes.
Use sm inside dense table rows, settings dialogs, or anywhere a single slider sits next to dense form controls. Use md as the default for stand-alone controls in a page or panel. Use lg when the slider is the primary affordance — a featured volume control on a media player, a centerpiece quality dial in an editor. Mixing sizes inside the same container is fine; the value type, the API, and the styling object are identical across all three.
1 <div className="space-y-4"> 2 <Slider defaultValue={40} size="sm" /> 3 <Slider defaultValue={50} size="md" /> 4 <Slider defaultValue={60} size="lg" /> 5 </div>
A disabled state for read-only previews
Pass disabled and the component applies opacity-50 cursor-default pointer-events-none to the wrapper, which freezes the thumb, dims the track, and stops every pointer event from reaching the resolver. The hidden range input keeps its current value, so a disabled Slider inside a form still submits — useful for "preview the current setting but do not let the user change it" rows in a billing page or an admin view.
The disabled visual matches the rest of the Drivn UI primitives — the same opacity floor as a disabled Button, the same cursor reset, the same pointer-events lock. Toggle the prop on a permission boolean and the row reads as informational when the user lacks edit access, and as interactive when they do.
1 "use client" 2 3 import { Slider } from "@/components/ui/slider" 4 5 export default function PlanLimit({ canEdit }: { canEdit: boolean }) { 6 return ( 7 <Slider 8 defaultValue={80} 9 disabled={!canEdit} 10 /> 11 ) 12 }
Install Drivn in one command
Copy the source into your project and own every line. Zero runtime dependencies, pure React + Tailwind.
npx drivn@latest createRequires Node 18+. Works with npm, pnpm, and yarn.
Frequently asked questions
Hold the value in useState and pass it as the value prop together with onChange. The component reads value !== undefined as the controlled signal, skips its internal state, and calls onChange on every drag step. For an uncontrolled slider, omit value and pass defaultValue instead — the component keeps the value in a useState slot internally and onChange becomes purely a notification callback.
Yes. Pass orientation="vertical" and the component swaps to the styles.vertical ruleset: base height of h-48, track running the full height, range filling from the bottom up. The size prop still applies — the matching vTrack key in styles.sizes (w-1, w-1.5, w-2 for sm/md/lg) sets the track width on the vertical axis. The current value still maps 0 → bottom, max → top.
No. Drivn's Slider takes a single number value and renders one thumb. If you need a low/high range slider — two thumbs, an array value — the shadcn/ui Slider built on @radix-ui/react-slider supports it out of the box; see the Drivn vs shadcn Slider comparison page for the trade-offs. The Drivn alternative is to compose two Slider instances with state that clamps each against the other.
Pass the step prop. The component's snap helper runs Math.round((val - min) / step) * step + min on every pointer move, so the thumb visibly jumps between positions and the onChange value is always already snapped. Use step={25} for four-position presets, step={1} on a 0–10 range for an integer rating control, step={0.1} on a 0–1 range for an opacity dial that rounds to one decimal.
Yes. Pass a name prop and the component renders a hidden <input type="range" name={name} value={current}> next to the visual thumb. The input is readOnly and tabIndex={-1} so it does not capture focus, but standard form submission picks the named field up. Works directly with Next.js server actions, plain action="/api/x" posts, and react-hook-form via Controller when you wire value and onChange.
Yes. The file starts with 'use client' because it uses useState for the uncontrolled value, useRef for the track measurement, and document-level pointermove/pointerup listeners for the drag gesture. You can still render it inside server-component pages — the parent stays server-side and the Slider hydrates on its own when the bundle reaches the browser.

