Skip to content
Suluu

SlideControl

A tactile range control. The thumb follows the pointer on a soft spring, and the fill trails it by a little. On release both settle onto the committed value.

Preview

Drag the thumb or click the track

Installation

Registry — recommended

Copies the component into your configured shadcn UI directory and installs Motion.

npx shadcn@latest add https://suluu.site/r/slide-control.json

npm package

For centralized upgrades, install the optional ESM package and configure Tailwind source detection as shown below.

pnpm add suluu motion

Usage

Give every slider a stable accessible name. Use defaultValue for local state, or pair value with onValueChange when your application owns the number.

Registry
import { SlideControl } from "@/components/ui/slide-control"

export function VolumeSetting() {
  return (
    <SlideControl
      aria-label="Volume"
      defaultValue={64}
      onValueChange={(value) => saveVolume(value)}
    />
  )
}
npm
// app/globals.css
@import "suluu/styles.css";
@source "../node_modules/suluu/dist";

// component.tsx
import { SlideControl } from "suluu/slide-control"

export function VolumeSetting() {
  return <SlideControl aria-label="Volume" defaultValue={64} />
}

Controlled state

Controlled
const [volume, setVolume] = useState(64)

<SlideControl
  aria-label="Volume"
  onValueChange={setVolume}
  value={volume}
/>

Compose with CounterNumbers

SlideControl does not render a value label. Pair it with CounterNumbers when the number should roll as the thumb settles.

Composition
import { CounterNumbers } from "@/components/ui/counter-numbers"
import { SlideControl } from "@/components/ui/slide-control"

const [budget, setBudget] = useState(2400)

<div>
  <CounterNumbers
    formatOptions={{ currency: "USD", style: "currency", maximumFractionDigits: 0 }}
    value={budget}
  />
  <SlideControl
    aria-label="Monthly budget"
    max={8000}
    min={400}
    onValueChange={setBudget}
    step={100}
    value={budget}
  />
</div>

In context

A budget cap stays readable as a rolling currency amount while the thumb settles.

Monthly budget

Caps the next billing period.

$2,400

Props

SlideControl accepts safe native div attributes, uses role="slider", and forwards its ref to the root element.

PropTypeDefaultDescription
valuenumberControlled committed value.
defaultValuenumberminInitial uncontrolled value.
onValueChange(value) => voidRuns when a drag, track press, or key press requests a new value.
minnumber0Inclusive lower bound of the range.
maxnumber100Inclusive upper bound of the range.
stepnumber1Distance between committed values. Use 0 to disable snapping.
disabledbooleanfalseDisables every interaction.
motionIntensity"subtle" | "default" | "expressive""default"Controls the thumb spring, fill follow, and squash.
classNamestringClass name applied to the slider.

Theming

Override these variables in your light and dark theme scopes. The registry installs the defaults automatically.

--suluu-slide-trackUnfilled track surface.
--suluu-slide-fillFilled track surface.
--suluu-slide-thumbThumb surface.
--suluu-slide-ringKeyboard focus ring.
--suluu-slide-offsetColor behind the focus ring offset.
--suluu-slide-track-shadowTrack inset depth.
--suluu-slide-fill-shadowFill elevation.
--suluu-slide-thumb-shadowThumb elevation.

The root exposes data-dragging while the pointer is captured.

Accessibility

The control is a focusable slider with live aria-valuenow, aria-valuemin, and aria-valuemax. Arrow keys move by step, Page Up and Page Down move by a tenth of the range (at least one step), and Home and End jump to the ends. Disabled state leaves the tab order and blocks every input path.

With reduced motion, dragging still works but the thumb settle, fill follow, and squash become immediate.

↑↓ to navigate↵ to openesc to close