Slider

Slider is the continuous-range control: a recessed SurfaceCut track, a gradient thumb that takes the theme accent, and a floating value bubble that lifts above the thumb while the user drags or focuses. Reach for it when the user cares about the proportion more than the exact digit — volume, brightness, opacity, font size, zoom, mix levels. A real <input type="range"> lives underneath, so arrow keys, Home/End, screen readers, and form submission all work out of the box.

<Slider value={volume} onChange={setVolume} className="w-72" />
<Slider
  value={bright}
  onChange={setBright}
  color="yellow"
  size="md"
  className="w-72"
/>
<Slider
  value={opacity}
  onChange={setOpacity}
  min={0}
  max={1}
  step={0.01}
  color="purple"
  className="w-72"
/>

Usage

import { Slider } from '@cladd-ui/react';
 
<Slider value={volume} onChange={setVolume} />;

Slider works in both controlled and uncontrolled modes. Pass value + onChange for controlled, or defaultValue for uncontrolled. onChange fires on every step while the user drags or arrows through values — pair it with debounce when the consumer is expensive (a network call, a heavy re-render in a sibling pane).

Slider, NumberField, or NumberScrubber?

Reach for Slider when the value is continuous and the user benefits from seeing where they are in the range — a 30 / 100 brightness reads at a glance from the filled segment. Reach for NumberField when the value is discrete and the user mostly nudges in steps (quantity, tip percent, page count) — the + / buttons own the writes. Reach for NumberScrubber when the value is arbitrary precision and the user wants both a draggable handle and a typeable input in the same compact slot — the inspector pattern from design tools (16px, 1.45, -0.4).

Examples

Sizes

size accepts sm (default) and md. Like its siblings Switch, Checkbox, and Radio, Slider ships only the two sizes that fit cleanly into a row of controls — a full 2xs → 2xl scale doesn't earn its keep on a track-and-thumb control. sm is the inline default; reach for md when the slider is the focal control of its row or the surrounding controls are md-sized.

<Slider size="md" value={a} onChange={setA} className="w-72" />
<Slider size="md" value={b} onChange={setB} className="w-72" />

Colors

color accepts any of the eleven cladd accent tokens — the accent drives both the filled segment of the track and the thumb's gradient fill. When unset, the slider inherits the theme accent from CladdProvider, so a brand-coloured slider doesn't usually need a color prop. Pass one when the slider signals semantics (red for "destructive volume", yellow for brightness) or when it sits alongside a non-default control that needs to read as a single unit.

<Slider
  color="brand"
  value={value}
  onChange={setValue}
  className="w-72"
/>

Min, max, step

min (default 0), max (default 100), and step (default 1) define the value space. The defaults are calibrated for percentage-shaped values; override all three for normalised ranges (0 → 1 with step={0.01} for opacity), wide ranges (25 → 400 with step={25} for zoom levels), or signed ranges (-2 → 8 for letter spacing). The thumb snaps to step on drag and on keyboard arrow presses.

<Slider value={pct} onChange={setPct} step={5} className="w-72" />
<Slider
  value={opacity}
  onChange={setOpacity}
  min={0}
  max={1}
  step={0.01}
  color="purple"
  className="w-72"
/>
<Slider
  value={zoom}
  onChange={setZoom}
  min={25}
  max={400}
  step={25}
  color="cyan"
  className="w-72"
/>

Value display

The slider renders a floating value bubble above the thumb while the user drags or focuses — handy for confirming the exact landing value mid-gesture. For the resting state, pair the slider with an external readout: a SectionTitle label on the left, the formatted value on the right, the track between them. Format the value however the unit calls for — raw integer for volume, xx% for normalised opacity, xxpx for size.

Volume
48
Opacity
70%
<div className="flex w-72 flex-col gap-4">
  <div className="flex flex-col gap-2">
    <div className="flex items-baseline justify-between">
      <SectionTitle>Volume</SectionTitle>
      <span className="text-cladd-fg-soft tabular-nums">{volume}</span>
    </div>
    <Slider value={volume} onChange={setVolume} color="brand" />
  </div>
  <div className="flex flex-col gap-2">
    <div className="flex items-baseline justify-between">
      <SectionTitle>Opacity</SectionTitle>
      <span className="text-cladd-fg-soft tabular-nums">
        {Math.round(opacity * 100)}%
      </span>
    </div>
    <Slider
      value={opacity}
      onChange={setOpacity}
      min={0}
      max={1}
      step={0.01}
      color="purple"
    />
  </div>
</div>

Debounce

debounce (in ms, default 0) delays the onChange call until the user pauses. The internal value still updates immediately, so the thumb tracks the drag with no visual lag — only the parent's onChange callback is throttled. Useful when the consumer is expensive: a colour-picker that re-renders a canvas, a search that hits an API, a Monaco editor that has to relayout. Drag the second slider below to feel the pause — the readout only catches up when you let go.

Immediate
40
Debounced 400ms
60
<div className="flex w-72 flex-col gap-4">
  <div className="flex flex-col gap-2">
    <div className="flex items-baseline justify-between">
      <SectionTitle>Immediate</SectionTitle>
      <span className="text-cladd-fg-soft tabular-nums">{immediate}</span>
    </div>
    <Slider value={immediate} onChange={setImmediate} color="brand" />
  </div>
  <div className="flex flex-col gap-2">
    <div className="flex items-baseline justify-between">
      <SectionTitle>Debounced 400ms</SectionTitle>
      <span className="text-cladd-fg-soft tabular-nums">{debounced}</span>
    </div>
    <Slider
      defaultValue={debounced}
      onChange={setDebounced}
      debounce={400}
      color="orange"
    />
  </div>
</div>

Disabled and read-only

disabled dims the whole slider to 50% and blocks all pointer and keyboard interaction — read as "not available right now" (the audio source is muted at the OS level, the layer is locked). readOnly is the subtler one: the slider keeps full opacity but stops responding to drags and keys — read as "live indicator, but you can't move it from here" (an enforced organisation policy, a value driven by a sibling control).

Disabled
Read-only
<div className="flex w-72 flex-col gap-2">
  <SectionTitle>Disabled</SectionTitle>
  <Slider defaultValue={30} disabled />
</div>
<div className="flex w-72 flex-col gap-2">
  <SectionTitle>Read-only</SectionTitle>
  <Slider defaultValue={70} readOnly color="red" />
</div>

Inspector row

The canonical design-tool pattern: a Surface panel with a label column, a slider column, and a value column on a single grid. Each row sets its own min / max / step because the unit is different (px for size, unitless for line-height, em-fractions for letter-spacing). The shared accent colour ties the group together; the value column does the formatting. This is what cladd was designed for: dense, but not crowded.

Typography
Size
14pxLine height
1.40Letter spacing
0.0
<div className="flex w-72 flex-col gap-2">
  <SectionTitle>Typography</SectionTitle>
  <div className="grid grid-cols-[6rem_1fr_3rem] items-center gap-2">
    <span className="text-cladd-fg-soft">Size</span>
    <Slider
      value={fontSize}
      onChange={setFontSize}
      min={8}
      max={64}
      color="brand"
    />
    <span className="text-right text-cladd-fg-soft tabular-nums">
      {fontSize}px
    </span>
    <span className="text-cladd-fg-soft">Line height</span>
    <Slider
      value={lineHeight}
      onChange={setLineHeight}
      min={1}
      max={2}
      step={0.05}
      color="brand"
    />
    <span className="text-right text-cladd-fg-soft tabular-nums">
      {lineHeight.toFixed(2)}
    </span>
    <span className="text-cladd-fg-soft">Letter spacing</span>
    <Slider
      value={letter}
      onChange={setLetter}
      min={-2}
      max={8}
      step={0.1}
      color="brand"
    />
    <span className="text-right text-cladd-fg-soft tabular-nums">
      {letter.toFixed(1)}
    </span>
  </div>
</div>

Playground

size, color, and the state toggles compose freely. The sm / md pair covers most cases; reach for md when the slider is the focal control of its row.

<Slider
  value={value}
  onChange={setValue}
  size="md"
  color="brand"
  disabled={false}
  readOnly={false}
  className="w-72"
/>

API Reference

className: stringExtra classes for the slider container.
color: ColorAccent color for the active track segment and thumb. Default: theme accent.
debounce: numberDebounce onChange calls in ms. Defaults to 0 (immediate).
defaultValue: number0Initial value (uncontrolled). Default 0.

Ignored when value is provided.
disabled: booleanVisually dim the slider and disable interaction.
input: booleanReserved - currently unused in the rendered output (the underlying <input type="range"> is always present). Kept for parity with other form components.
max: number100Default 100.
min: number0Default 0.
onChange: (value: number, event?: ChangeEvent<HTMLInputElement>) => voidFires while the user drags or types a new value (subject to debounce).
readOnly: booleanBlock dragging without the disabled visual treatment.
size: SliderSize'sm'Slider size token. Drives track and thumb dimensions. Default 'sm'.
step: number1Default 1.
value: numberControlled value. When omitted, the component falls back to uncontrolled mode using defaultValue.