Skip to content
Drivn
6 min read

Drivn vs shadcn/ui — Slider Compared

Drivn Slider vs shadcn/ui — zero-runtime pointer-events handler with sm/md/lg size prop versus the Radix UI primitive that ships multi-thumb range support.

Drivn's Slider and shadcn/ui's Slider answer the same question — how do you let a user drag a thumb along a track to pick a number — but they answer it with two very different files. shadcn/ui wraps @radix-ui/react-slider, the headless primitive that ships with Slider.Root, Slider.Track, Slider.Range, and Slider.Thumb sub-components plus full keyboard navigation, multi-thumb range support, and ARIA wiring baked in. Drivn ships a single ~120 line client component that reads pointer events directly off the track, snaps the value, and updates a controlled or uncontrolled number — no Radix package, no peer dependency, no headless layer.

The trade-off is explicit. The Radix-backed shadcn Slider gives you a battle-tested keyboard story and array-valued range sliders (two thumbs, a low and a high) the moment you reach for them, at the cost of pulling @radix-ui/react-slider into your bundle and reading the primitive's compound API. Drivn's Slider gives you a single value: number prop, an onChange: (value: number) => void callback, a built-in size union of 'sm' | 'md' | 'lg', an orientation of 'horizontal' | 'vertical', and zero runtime UI dependencies — but only one thumb, and the pointer-events implementation is the file you own.

This page walks the dependency story, the API shape, the pointer-events handler, the size and orientation tokens, and the situations where each Slider is the right pick. The complete Drivn source lives at packages/drivn/src/registry/components/slider.ts; the Drivn CLI writes it into your project as @/components/ui/slider.tsx.

Side-by-side comparison

FeatureDrivnshadcn/ui
Underlying implementationSingle client component, pointer events on the trackWraps @radix-ui/react-slider primitive (Slider.Root/Track/Range/Thumb)
Runtime UI dependenciescn() utility only — zero npm packages@radix-ui/react-slider
Public API shapeFlat — <Slider value={n} onChange={fn} />Compound — <Slider value={[n]} onValueChange={fn} />
Value typenumber (single thumb)number[] (multi-thumb capable)
Multi-thumb rangeNot supported — one thumbBuilt-in via array value
Built-in size prop'sm' | 'md' | 'lg' (sm/md/lg track + thumb)No size prop — adjust via className
Orientation'horizontal' | 'vertical''horizontal' | 'vertical'
Keyboard navigationHidden range input (read-only) — pointer/touch onlyFull arrow-key support via Radix
Form integrationHidden <input type="range" name={name}>Hidden input via Radix
Client componentYes — 'use client'Yes — 'use client'
LicenseMITMIT
Copy-paste install

Zero Radix dependency, one file you own

shadcn/ui's Slider is a thin styling layer on top of @radix-ui/react-slider. The primitive does the work — track sizing, thumb positioning, drag gestures, keyboard navigation, ARIA attributes — and the shadcn file maps Radix sub-components to Tailwind classes. The trade-off is one extra package in your package.json, one extra entry in your lockfile, and one extra peer dependency moving on its own release cadence.

Drivn's Slider has no Radix. The file imports React and the local cn() utility, declares a styles object with base/track/range/thumb/horizontal/vertical/sizes/disabled keys, and renders three nested divs plus a hidden range input. Drag handling is a handlePointerDown callback that captures pointer move/up listeners on document, reads getBoundingClientRect() off a trackRef, computes the ratio, runs it through snap(val, min, step), and calls onChange. The whole component is about 120 lines and ships with the Drivn CLI directly into your project.

1// packages/drivn/src/registry/components/slider.ts — verbatim excerpt
2function snap(val: number, min: number, step: number) {
3 return Math.round((val - min) / step) * step + min
4}
5
6function handlePointerDown(e: React.PointerEvent) {
7 if (disabled) return
8 e.preventDefault()
9 resolve(e.clientX, e.clientY)
10 const onMove = (ev: PointerEvent) =>
11 resolve(ev.clientX, ev.clientY)
12 const onUp = () => {
13 document.removeEventListener('pointermove', onMove)
14 document.removeEventListener('pointerup', onUp)
15 }
16 document.addEventListener('pointermove', onMove)
17 document.addEventListener('pointerup', onUp)
18}

Flat API instead of compound sub-components

The shadcn Slider follows the Radix compound pattern: <Slider value={[50]} onValueChange={setValue} max={100} step={1} />, where the value is an array — even for a single thumb — because the same primitive renders multi-thumb range sliders. The wrapper component composes Slider.Root, Slider.Track, Slider.Range, and one Slider.Thumb per array entry.

Drivn's API is flat. <Slider value={50} onChange={setValue} max={100} step={1} /> — a single number value, a callback that receives a number, no array wrapping. The component renders one thumb, one range fill, one track, and that is the contract. If you need a low/high range slider with two thumbs, Drivn does not have it and shadcn does. If you only ever need one thumb (a volume knob, a brightness setting, a single price ceiling, an opacity dial), Drivn's flat API is one fewer destructure per render. Use the Drivn CLI to drop the file into @/components/ui/slider.tsx.

1"use client"
2
3import { useState } from "react"
4import { Slider } from "@/components/ui/slider"
5
6export default function VolumePanel() {
7 const [volume, setVolume] = useState(50)
8
9 return (
10 <Slider
11 value={volume}
12 onChange={setVolume}
13 max={100}
14 />
15 )
16}

A built-in size token, not a className guess

The shadcn Slider has no size prop. The track and thumb dimensions are baked into the component file, and changing them means either editing the file or wrapping it. That is consistent with the rest of shadcn — the file is yours after copy-paste — but it leaves the question of "how do I make this 25% smaller" to the caller every time.

Drivn ships a size prop typed as keyof typeof styles.sizes — autocompleting to 'sm' | 'md' | 'lg'. sm is a 4-pixel track with a 12-pixel thumb, md is 6-pixel/16-pixel (the default), lg is 8-pixel/20-pixel. The mapping is in the styles.sizes object, so changing the visual scale across the project is one edit. The same union covers horizontal and vertical orientation — vTrack keys give the track width when the slider runs vertically. Pair the small size with a Label and the large size as a centrepiece control in a settings panel.

1// Drivn styles.sizes — verbatim from the registry
2sizes: {
3 sm: { track: 'h-1', thumb: 'w-3 h-3', vTrack: 'w-1' },
4 md: { track: 'h-1.5', thumb: 'w-4 h-4', vTrack: 'w-1.5' },
5 lg: { track: 'h-2', thumb: 'w-5 h-5', vTrack: 'w-2' },
6},
7
8// Usage
9<Slider defaultValue={40} size="sm" />
10<Slider defaultValue={50} size="md" />
11<Slider defaultValue={60} size="lg" />

Where Radix wins: keyboard and multi-thumb

There are two situations where the shadcn/ui Slider is the right pick. The first is keyboard navigation. Radix's primitive wires arrow keys to step the value, Home/End to jump to min/max, and PageUp/PageDown to step by a larger increment, all out of the box. Drivn renders a hidden <input type="range"> for form submission with tabIndex={-1} and readOnly — the input ships the value, but the focus and key handling live on the visual thumb only as ARIA hints. If your slider is the primary control on a page where keyboard-only users will land, Radix's wiring is worth the extra package.

The second is multi-thumb range sliders. A price filter that takes a low and a high, an audio editor with start/end markers, a graphics tool with a histogram range — shadcn's array-valued API renders two thumbs and tracks them independently from one call. Drivn's single-number API does not. For one-thumb controls (volume, brightness, opacity, single-threshold filters), Drivn is leaner; for two-thumb controls, reach for shadcn or compose two Drivn Sliders side-by-side with linked state.

1// shadcn/ui multi-thumb range — for reference
2"use client"
3import { useState } from "react"
4import { Slider } from "@/components/ui/slider"
5
6export function PriceRange() {
7 const [range, setRange] = useState([20, 80])
8
9 return (
10 <Slider
11 value={range}
12 onValueChange={setRange}
13 min={0}
14 max={100}
15 />
16 )
17}
Get started

Install Drivn in one command

Copy the source into your project and own every line. Zero runtime dependencies, pure React + Tailwind.

npx drivn@latest create

Requires Node 18+. Works with npm, pnpm, and yarn.

Enjoying Drivn?
Star the repo on GitHub to follow new component releases.
Star →

Frequently asked questions

No. The component imports React and the local cn() utility — nothing else. Drag handling reads pointer events off a trackRef directly, value snapping is a four-line helper inside the file, and ARIA attributes are spread onto the thumb div. The entire file is around 120 lines and ships with the Drivn CLI as @/components/ui/slider.tsx. No @radix-ui/react-slider, no peer dependency, no extra entry in package.json.

Not from one component instance. Drivn's Slider takes a single number value and renders one thumb, by design. If you need a low/high range slider, you have two options: reach for shadcn/ui where the Radix primitive supports an array value out of the box, or compose two Drivn Sliders with linked state — one controlling the low end, one the high, and onChange handlers that clamp each against the other.

Pass the size prop. The union is "sm" | "md" | "lg" and maps to the styles.sizes object in the component file: sm is a 4-pixel track with a 12-pixel thumb, md (the default) is 6/16, lg is 8/20. The same mapping covers vertical orientation — vTrack keys set the track width when orientation="vertical". For finer control, pass a className; cn() merges with the size classes using later-class precedence so your utility wins.

Partially. The component renders a hidden <input type="range"> with the current value, min, max, and step for form submission, but it is read-only and tabIndex={-1}, so arrow keys on the visual thumb do not step the value the way Radix does. If keyboard-only navigation is critical for the page — for example, a settings dialog where the slider is the primary control — the shadcn/ui Slider built on @radix-ui/react-slider is the safer choice. Pointer and touch input on Drivn work end-to-end.

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. Rendering it inside a server component means the parent stays server-side and the Slider hydrates on its own. If you need a server-rendered placeholder before hydration, pair it with a Skeleton via the Skeleton component or render a static thumb inside a Suspense fallback.

Yes. Pass a name prop and the component renders a hidden <input type="range" name={name} value={current}> alongside the visual thumb. The input is read-only and not focusable, but standard form submission picks it up — useful with the Form, Label, and form-state patterns from the Drivn docs. For controlled forms with react-hook-form, wire value and onChange to the field's register or Controller as usual; the name prop becomes optional.