docs / core / slider

Slider

one number, picked along a track by feel: a speed, an opacity, a blur radius. when the exact digits matter, ask with Input instead.

intensity · recedesrsc · client

provenance

authored in usva

layer

core / primitives

intensity

recedes · safe anywhere, including dense surfaces

composition

settings rows, filter panels, anywhere a value is tuned by feelpair label with showValue so the number stays visible while draggingnot a range picker. one thumb, one numbernot a substitute for a typed field when precision matters

a11y

role="slider" named by its label · arrow keys, Home and End move the thumb · focus ring on the thumbjest-axe

dependencies

@base-ui/react · class-variance-authority
live demo · try it out
Opacity
80%
customize
labeltext above the track
sizesm is for dense rows
minlower bound
0
maxupper bound
100
stepincrement per tick
5
defaultValuestarting position
80
suffixunit appended to the readout
showValuevalue readout on the right
disableddims and ignores input
usagecopy
import { Slider } from "usva/primitives/slider";

<Slider
  label="Opacity"
  defaultValue={80}
  step={5}
  showValue
  formatValue={(v) => `${v}%`}
/>

props

proptypedefaultnotes
valuenumbercontrolled value.
defaultValuenumberinitial value when uncontrolled.
onValueChange(value: number, event) => voidfires on every change, mid-drag and per key press. use onValueCommitted for release only.
minnumber0lower bound.
maxnumber100upper bound.
stepnumber1increment per arrow key press and per drag tick.
disabledbooleanfalsedims to 50% and ignores pointer and keyboard.
labelReactNodelabel above the track. also names the slider for screen readers.
showValuebooleancurrent value on the right of the label row.
formatValue(value: number) => stringformats the readout. without it, Base UI's own formatting is used.
size"sm" | "md""md"track and thumb size. sm is for dense rows.

get it

npx shadcn add https://usva.build/r/slider.jsoncopy
source · components/ui/slider.tsxexactly what this command copiescopy
"use client";
import { Slider as Base, type SliderRootProps } from "@base-ui/react/slider";
import { cva, type VariantProps } from "class-variance-authority";
import * as React from "react";
import { cn } from "@/lib/utils";

const trackVariants = cva(
  "relative w-full select-none rounded-full border border-border bg-surface-2",
  {
    variants: {
      size: {
        sm: "h-1",
        md: "h-1.5",
      },
    },
    defaultVariants: { size: "md" },
  },
);

const thumbVariants = cva(
  cn(
    "rounded-full border border-accent bg-gradient-accent shadow-raised outline-none transition-control duration-base ease-soft hover:glow-ring has-[:focus-visible]:ring-focus data-[dragging]:glow-ring",
    "after:absolute after:content-['']",
  ),
  {
    variants: {
      size: {
        sm: "size-3.5 after:-inset-[5px]",
        md: "size-4 after:-inset-1",
      },
    },
    defaultVariants: { size: "md" },
  },
);

const controlVariants = cva("flex w-full touch-none items-center py-1.5", {
  variants: {
    size: {
      sm: "px-[7px]",
      md: "px-2",
    },
  },
  defaultVariants: { size: "md" },
});

export interface SliderProps
  extends Omit<SliderRootProps<number>, "render" | "className">,
    VariantProps<typeof trackVariants> {
  className?: string;
  label?: React.ReactNode;
  showValue?: boolean;
  formatValue?: (value: number) => string;
}

export const Slider = React.forwardRef<HTMLDivElement, SliderProps>(
  (
    { className, size, label, showValue, formatValue, disabled, ...props },
    ref,
  ) => (
    <Base.Root
      ref={ref}
      disabled={disabled}
      className={cn(
        "flex w-full flex-col gap-2",
        disabled && "cursor-not-allowed opacity-50",
        className,
      )}
      {...props}
    >
      {label || showValue ? (
        <div className="flex items-center justify-between gap-4">
          {label ? (
            <Base.Label className="text-sm text-ink select-none">
              {label}
            </Base.Label>
          ) : (
            <span />
          )}
          {showValue ? (
            <Base.Value className="font-mono text-xs tabular-nums text-muted">
              {(formatted, values) =>
                formatValue ? formatValue(values[0] ?? 0) : (formatted[0] ?? "")
              }
            </Base.Value>
          ) : null}
        </div>
      ) : null}
      <Base.Control className={controlVariants({ size })}>
        <Base.Track className={trackVariants({ size })}>
          <Base.Indicator className="rounded-full bg-accent bg-gradient-accent" />
          <Base.Thumb className={thumbVariants({ size })} />
        </Base.Track>
      </Base.Control>
    </Base.Root>
  ),
);
Slider.displayName = "Slider";