How to Add a Slider to a React App
Step-by-step guide to adding a range slider to any React app with the Drivn CLI — a copy-paste, pointer-driven control with a hidden range input, zero deps.
Reaching for a slider means your React app needs a drag-to-set control — a volume knob, a price filter, a brightness setting, any value the user picks by sliding a thumb along a track. You could bolt an onChange onto a native <input type="range"> and fight its browser-default styling, or you could hand-roll pointer math and end up re-solving thumb positioning, value snapping, and the controlled/uncontrolled split yourself. Neither leaves you with something that matches the rest of your design system out of the box.
Drivn solves it by copy-paste: the CLI writes a slider.tsx file into your repository. The Slider reads pointer coordinates off the track, maps them to a value between min and max, snaps that value to the nearest step, and calls onChange with a plain number. Because it tracks a value in React.useState and listens for pointermove while you drag, the file opens with a 'use client' directive and renders inside a client boundary. Alongside the visible thumb it renders a hidden <input type="range" name={...}>, so the value posts with a form the way a native range input would.
This guide adds the Slider to any React app — install the CLI, render your first control, drive it with state, and size, orient, and restyle it from the file you own. For the App Router walkthrough see the Next.js Slider guide, and for copy-paste layouts see the Slider examples.
Prerequisites
Before installing Slider, confirm your React project has the two things Drivn assumes: Tailwind CSS v4 installed and processing your styles, and a @/ path alias pointing at your source directory (the component ships as a .tsx file, so TypeScript is part of the stack). If you scaffolded with create-next-app, npm create vite, or npx drivn@latest create, both are already wired; for a custom setup the installation page lists the minimal config. The Slider's only import past React is the local cn utility for class merging — there is no third-party package to add and no icon dependency. The one detail worth knowing before you render it is that the Slider is a Client Component: it holds its value in state and reads pointer events off a ref, so its source opens with a 'use client' directive and renders inside a client boundary rather than straight in a Server Component.
Step 1 — Install Drivn via the CLI
Run the CLI from your project root to add the Slider source file. The command prompts once for your install directory (defaulting to src/components/ui/) and writes a single slider.tsx — no other files, because the component has no peer dependency beyond React and the cn utility. No global config file is created; the result is TypeScript source you edit like any other component. Confirm it landed in your editor, then commit it for a clean baseline. If your project uses a monorepo layout or a non-standard source path, the CLI docs cover the flags for targeting a custom location during install. Because the file is yours after install, future Drivn releases will not overwrite your edits — the whole control, from the pointer math to the styling object, is one file you can read top to bottom.
1 # add the Slider source file 2 npx drivn add slider
Step 2 — Render your first slider
Import Slider and render it inside a Client Component. The simplest form is uncontrolled: pass defaultValue and let the component keep the value internally, reading onChange only when you need to react to a change. A bare <Slider defaultValue={50} /> gives you a working 0–100 control — those are the default min and max — with a draggable thumb, a filled range that tracks it, and click-to-set anywhere on the track. The example below wraps a single slider with a heading so it reads as a settings row. Because the file opens with 'use client', drop this into a client component or a page that already sits inside a client boundary; the Next.js Slider guide covers importing it into a Server Component page. Every dimension the control needs is built in, so there is no width or track styling to add before it works.
1 'use client' 2 3 import { Slider } from '@/components/ui/slider' 4 5 export function VolumeControl() { 6 return ( 7 <div className="w-full space-y-2"> 8 <div className="text-sm font-medium">Volume</div> 9 <Slider defaultValue={50} /> 10 </div> 11 ) 12 }
Step 3 — Control the value with state
To read the value elsewhere — a live label, a preview, a synced second control — run the Slider controlled. Hold the number in useState, pass it as value, and pass your setter as onChange; the component renders exactly the number you hand it and reports the next snapped value on every drag. The snap respects min, max, and step: with step={10} on the default 0–100 range the thumb lands on the nearest ten, so onChange only ever fires 0, 10, 20, and so on. The example reads the live value into a label above the track, so the readout tracks the thumb in real time. Reach for controlled whenever the value needs to be visible outside the slider; stay uncontrolled with defaultValue when the slider owns its own value and you only read it on submit.
1 'use client' 2 3 import { useState } from 'react' 4 import { Slider } from '@/components/ui/slider' 5 6 export function BrightnessRow() { 7 const [brightness, setBrightness] = useState(75) 8 9 return ( 10 <div className="w-full space-y-2"> 11 <div className="text-sm font-medium"> 12 Brightness {brightness}% 13 </div> 14 <Slider 15 value={brightness} 16 onChange={setBrightness} 17 /> 18 </div> 19 ) 20 }
Step 4 — Size, orient, submit, and restyle
The Slider ships three sizes and two orientations, all as props. size is 'sm' | 'md' | 'lg' and scales the track height and thumb diameter together — sm is a 1-pixel track with a 3-unit thumb, lg a 2-pixel track with a 5-unit thumb. orientation="vertical" rotates the whole control into a default h-48 column with the thumb sliding up for higher values. Give it a name prop and it renders a hidden <input type="range" name={name}> that mirrors the value, so it posts with any surrounding <form> — including one wired to a react-hook-form Controller. Everything visible is a Tailwind class in the styles object inside the file you own: the track is bg-border, the filled range and the thumb are bg-foreground. Swap the range and thumb to bg-primary for a branded control and every Slider follows, matching dark and light mode with no override. For copy-paste variants see the Slider examples.
1 <Slider defaultValue={40} size="sm" /> 2 <Slider defaultValue={50} size="md" /> 3 <Slider defaultValue={60} size="lg" /> 4 <Slider defaultValue={50} orientation="vertical" />
Install Drivn in one command
Copy the source into your project and own every line. Zero runtime dependencies, pure React + Tailwind.
Frequently asked questions
Yes. The Slider holds its value in React state and reads pointer coordinates off a ref, listening for pointermove on the document while you drag — so its source opens with a 'use client' directive and renders inside a Client Component. In the Next.js App Router, put the Slider in a file that starts with 'use client' (or import one that does). In a Vite or Create React App project the directive is simply ignored and the component works the same.
The Slider is controlled when you pass a value prop and uncontrolled when you pass defaultValue instead. Controlled means you store the value in state and update it from onChange; the component renders exactly what you hand it. Uncontrolled means the component keeps its own internal value and still calls onChange on each change. Reach for controlled when another element — a live label, a preview, a synced control — needs to read the same value.
Pass min, max, and step as numbers. They default to 0, 100, and 1. The component maps the thumb position to a value in that range and snaps it to the nearest step, so step={10} on a 0–100 range makes onChange report only 0, 10, 20, and so on. Set min and max to any bounds — min={-50} max={50} for a balance control — and the thumb positions itself proportionally along the track.
Give the Slider a name prop. Alongside the visible track it renders a hidden <input type="range" name={name}> that mirrors the current value, so it posts with any surrounding <form> — including one wired to a Next.js Server Action. On submit the value arrives in FormData under that name, and you read it server-side the same way you would a native range input. For react-hook-form, wire value and onChange through a Controller.
Yes. The component depends only on React and the local cn utility — no Next.js API and no router — so it works anywhere React and Tailwind reach the DOM. Set the project up with TypeScript and Tailwind v4 first, then run npx drivn add slider and render it the same way, driving it with value and onChange or leaving it uncontrolled with defaultValue.

