Free shadcn/ui slider component for React — built on Radix. A draggable range input: a single thumb, a two-thumb range, a value label, stepped ticks, and a volume control.

Installation

1 Configure registry — once per project
{
  "registries": {
    "@designrevision": "https://registry.designrevision.com/r/{name}.json"
  }
}

Add to your existing components.json. Requires shadcn/ui v2.3+.

2 Install the component

Packages

@radix-ui/react-slider
utils

Props

defaultValue / value = [min, max]
number[]

Thumb positions. One number = one thumb; two = a range slider.

min / max / step = 0 / 100 / 1
number

Range bounds and increment.

orientation = "horizontal"
"horizontal" | "vertical"

Slider direction.

A Radix slider. Pass an array to value / defaultValue — one number renders a single thumb, two render a range slider. Set min, max, step for the scale and onValueChange to read it. Works horizontally or vertically (orientation="vertical" needs a height).

Copied!
components/ui/slider.tsx
"use client"

import * as React from "react"
import * as SliderPrimitive from "@radix-ui/react-slider"

import { cn } from "@/lib/utils"

function Slider({
  className,
  defaultValue,
  value,
  min = 0,
  max = 100,
  ...props
}: React.ComponentProps<typeof SliderPrimitive.Root>) {
  const _values = React.useMemo(
    () =>
      Array.isArray(value)
        ? value
        : Array.isArray(defaultValue)
          ? defaultValue
          : [min, max],
    [value, defaultValue, min, max]
  )

  return (
    <SliderPrimitive.Root
      data-slot="slider"
      defaultValue={defaultValue}
      value={value}
      min={min}
      max={max}
      className={cn(
        "relative flex w-full touch-none items-center select-none data-[disabled]:opacity-50 data-[orientation=vertical]:h-full data-[orientation=vertical]:min-h-44 data-[orientation=vertical]:w-auto data-[orientation=vertical]:flex-col",
        className
      )}
      {...props}
    >
      <SliderPrimitive.Track
        data-slot="slider-track"
        className={cn(
          "bg-muted relative grow overflow-hidden rounded-full data-[orientation=horizontal]:h-1.5 data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-1.5"
        )}
      >
        <SliderPrimitive.Range
          data-slot="slider-range"
          className={cn(
            "bg-primary absolute data-[orientation=horizontal]:h-full data-[orientation=vertical]:w-full"
          )}
        />
      </SliderPrimitive.Track>
      {Array.from({ length: _values.length }, (_, index) => (
        <SliderPrimitive.Thumb
          data-slot="slider-thumb"
          key={index}
          className="border-primary bg-background ring-ring/50 block size-4 shrink-0 rounded-full border shadow-sm transition-[color,box-shadow] hover:ring-4 focus-visible:ring-4 focus-visible:outline-hidden disabled:pointer-events-none disabled:opacity-50"
        />
      ))}
    </SliderPrimitive.Root>
  )
}

export { Slider }

Examples

Basic

The canonical single-thumb slider — drag to pick one value.

Packages

slider

Props

No props documented yet.

Copied!
components/ui/slider-demo-01.tsx
import { Slider } from "@/components/ui/slider"

export default function SliderDemo01() {
  return (
    <Slider
      defaultValue={[50]}
      max={100}
      step={1}
      className="w-full max-w-sm"
    />
  )
}

Range

Two thumbs select a min and max, with a live price readout.

Packages

slider

Props

No props documented yet.

Copied!
components/ui/slider-range-01.tsx
"use client"

import * as React from "react"

import { Slider } from "@/components/ui/slider"

export default function SliderRange01() {
  const [value, setValue] = React.useState([200, 800])

  return (
    <div className="w-full max-w-sm space-y-3">
      <div className="flex items-center justify-between text-sm">
        <span className="text-muted-foreground">Price range</span>
        <span className="font-medium tabular-nums">
          ${value[0]} – ${value[1]}
        </span>
      </div>
      <Slider
        value={value}
        onValueChange={setValue}
        min={0}
        max={1000}
        step={10}
      />
    </div>
  )
}

With value label

A controlled slider that shows its current value beside a label.

Packages

slider
label

Props

No props documented yet.

Copied!
components/ui/slider-value-01.tsx
"use client"

import * as React from "react"

import { Label } from "@/components/ui/label"
import { Slider } from "@/components/ui/slider"

export default function SliderValue01() {
  const [value, setValue] = React.useState([33])

  return (
    <div className="w-full max-w-sm space-y-3">
      <div className="flex items-center justify-between">
        <Label>Brightness</Label>
        <span className="text-sm font-medium tabular-nums">{value[0]}%</span>
      </div>
      <Slider value={value} onValueChange={setValue} max={100} step={1} />
    </div>
  )
}

Stepped with ticks

Snaps to fixed steps, with tick labels marked below the track.

Packages

slider

Props

No props documented yet.

Copied!
components/ui/slider-steps-01.tsx
import { Slider } from "@/components/ui/slider"

const ticks = [0, 20, 40, 60, 80, 100]

export default function SliderSteps01() {
  return (
    <div className="w-full max-w-sm space-y-3">
      <Slider defaultValue={[40]} max={100} step={20} />
      <div className="text-muted-foreground flex justify-between text-xs tabular-nums">
        {ticks.map((tick) => (
          <span key={tick}>{tick}</span>
        ))}
      </div>
    </div>
  )
}

Volume control

A slider flanked by mute and volume icons — the media-control pattern.

Packages

lucide-react
slider

Props

No props documented yet.

Copied!
components/ui/slider-volume-01.tsx
"use client"

import * as React from "react"
import { Volume2, VolumeX } from "lucide-react"

import { Slider } from "@/components/ui/slider"

export default function SliderVolume01() {
  const [volume, setVolume] = React.useState([60])

  return (
    <div className="flex w-full max-w-sm items-center gap-3">
      <VolumeX className="text-muted-foreground size-4 shrink-0" />
      <Slider
        value={volume}
        onValueChange={setVolume}
        max={100}
        step={1}
        aria-label="Volume"
      />
      <Volume2 className="text-muted-foreground size-4 shrink-0" />
    </div>
  )
}

Frequently asked questions

It is an accessible range input built on @radix-ui/react-slider. You drag a thumb along a track to pick a numeric value (or two thumbs for a range). Radix handles the keyboard, focus, dragging, and ARIA wiring; the shadcn styling gives it the track, range, and thumb look.
Pass an array with two numbers as the value: . Radix renders one thumb per array entry, so a two-value array gives you a min/max range slider. Read both back from onValueChange as an array.
Control it: const [value, setValue] = React.useState([50]), then . onValueChange fires with the full array on every drag, so value[0] is the current number. Use defaultValue instead if you only need the value on submit.
Use the step, min, and max props: . The thumb snaps to multiples of step between min and max. For a labelled scale, render your own tick marks below the track and space them to match the steps.
Yes — pass orientation="vertical" and give it a height: . Radix swaps the drag axis and keyboard arrows accordingly. Horizontal is the default and fits most forms.
Yes. Each thumb is focusable and responds to Arrow keys (step), Page Up / Page Down (larger jumps), and Home / End (min / max). Radix sets role="slider" with aria-valuemin, aria-valuemax, and aria-valuenow, so screen readers announce the current value. Add an aria-label when there is no visible label.