|Kit

Slider

Single-value or two-thumb range input on top of Ark UI's slider primitives. Marks, floating label, sizes, colors — keyboard, RTL, and ARIA handled upstream.

The Slider wraps Ark UI's Slider state machine, so keyboard navigation (←/→, Home/End, PageUp/PageDown), RTL flipping, and ARIA wiring come for free. The wrapper layers a styled track / range / thumb, optional marks, and a floating label that follows each thumb.

Pass a number for a single thumb, or a [number, number] tuple for a two-thumb range. The value shape flows through the whole API — the callbacks echo back the same shape.

import { Slider } from '@42/ui-react/slider';

<Slider defaultValue={42} label="42%" />        {/* single thumb */}
<Slider defaultValue={[25, 60]} />              {/* two-thumb range */}

Playground

Component

Loading preview

Modes

Props

Code

<Component size="md" />

Sizes

Five track thicknesses — xs through xl. The thumb scales to match.

Code

<Slider size="xs" defaultValue={42} /><Slider size="sm" defaultValue={42} /><Slider size="md" defaultValue={42} /><Slider size="lg" defaultValue={42} /><Slider size="xl" defaultValue={42} />

Colors

Color is orthogonal to the rest of the slider — the accent rides on data-color and the range/thumb reference --c-solid, so every palette in the kit flows through for free, including dark mode and consumer-defined palettes. brand is the default; see Colors for the full system.

brand
gray
red
orange
yellow
green
teal
cyan
blue
purple
pink

Code

import { COLORS } from '@42/ui-react';// ['brand','gray','red','orange','yellow','green','teal','cyan','blue','purple','pink']{COLORS.map((c) => (<Slider key={c} color={c} defaultValue={60} />))}

Marks

Pass marks to paint dots along the track. Each mark is { value, label? } — the label is rendered below the dot. Marks don't restrict input; they're purely visual unless you pair them with a matching step.

Code

<SliderdefaultValue={50}marks={[  { value: 0,   label: '0' },  { value: 25,  label: '25' },  { value: 50,  label: '50' },  { value: 75,  label: '75' },  { value: 100, label: '100' },]}/>

For a segmented stepper, combine marks with step:

<Slider
  min={0}
  max={10}
  step={1}
  defaultValue={4}
  marks={Array.from({ length: 11 }, (_, i) => ({ value: i }))}
/>

Min, max & step

min, max, and step define the value space — nothing here assumes 0–100. The label, marks, and keyboard steps all read from it, so a fractional step and a unit-formatted label compose without extra wiring.

Code

<Slider min={16} max={30} step={0.5} defaultValue={21} label={(v) => `${v}°C`} />

Range

Pass a [number, number] tuple instead of a single number and the slider renders two thumbs with the filled range between them — for price bands, time windows, or any min/max selection. Everything else carries over: each thumb gets its own label, marks between the thumbs read as active, and the callbacks hand you back a [number, number].

Use minStepsBetweenThumbs to keep the thumbs from crossing or sitting on top of each other.

Code

<Slider defaultValue={[25, 60]} minStepsBetweenThumbs={5} />

Controlled, the value stays a tuple end to end — onChange receives [number, number], no shape guessing:

'use client';
import { useState } from 'react';
import { Slider } from '@42/ui-react/slider';

export function PriceRange() {
  const [range, setRange] = useState<[number, number]>([25, 60]);
  return (
    <Slider
      value={range}
      onChange={setRange}
      onChangeEnd={(v) => filterByPrice(v)}
      minStepsBetweenThumbs={5}
      label={(v) => `$${v}`}
    />
  );
}

Origin

By default the filled range grows from the start of the track. Set origin="center" to fill outward from the midpoint — the right shape for balance, pan, or any signed adjustment around a neutral zero.

Code

<Slider min={-50} max={50} defaultValue={0} origin="center" />

Floating label

label accepts any ReactNode and floats above the thumb. By default it appears on hover, focus, or drag (showLabelOnHover). Set it to never or false to hide it.

Code

<Slider label="35%" />                              {/* on hover/focus */}<Slider label="35%" showLabel="always" />           {/* always visible */}<Slider showLabel="never" />                        {/* no label */}<Slider label={null} />                             {/* no label */}

The label must be serializable. To bind it to the current value, drive the slider as a controlled input from a client component (see below).

Controlled

onChange fires on every change (drag, keyboard, click). onChangeEnd fires once the interaction settles — use it to debounce expensive work (network calls, expensive re-renders downstream). Drag the example below: value tracks every tick, committed only updates on release.

value 50%

committed 50%

A controlled value is also how you wire a dynamic label, since the label has to be computed in the same client tree as the slider:

'use client';
import { useState } from 'react';
import { Slider } from '@42/ui-react/slider';

export function VolumeSlider() {
  const [value, setValue] = useState(50);
  return (
    <Slider
      value={value}
      onChange={setValue}
      onChangeEnd={(v) => commitToServer(v)}
      label={`${value}%`}
    />
  );
}

Disabled & read-only

disabled dims the track and blocks all interaction. readOnly keeps full contrast and stays focusable — so the value is still announced to assistive tech — but rejects changes. Reach for readOnly to show a value the user can inspect but not edit.

Code

<Slider defaultValue={60} disabled /><Slider defaultValue={60} readOnly />

Forms

Each thumb renders a hidden <input>, so a name wires the slider into native <form> submission and FormData — no controlled state required.

Code

<form action={save}><Slider name="volume" defaultValue={60} /></form>

Styling parts

Every part takes a class override through classNamesroot, control, track, range, thumb, thumbLabel, and the mark slots. Use it to retheme a one-off without forking the component; the data-color system still drives anything you don't override.

Code

<SliderdefaultValue={65}classNames={{  track: 'bg-purple-100 dark:bg-purple-950',  range: 'bg-purple-500',  thumb: 'border-purple-500',}}/>

API

Prop

Type

All other props are forwarded to the underlying Ark Slider.Root.

Accessibility

  • Keyboard: / step by step; Shift + ←/→ jumps by 10×; Home / End snap to min / max; PageUp / PageDown jump by 10×.
  • Range: each thumb is an independent, focusable control — Tab moves between them, and arrows drive whichever is focused. minStepsBetweenThumbs enforces the gap so they never cross.
  • RTL: when the document dir is rtl (or a parent sets it), Ark inverts the thumb direction so still increases the value visually.
  • Focus ring is keyboard-only (focus-visible:) so pointer interactions stay quiet.
  • A hidden <input> is rendered inside each thumb so the slider integrates with native <form> submission. Pass name to wire it up.

On this page