Slider

Pick a value along a range. A restyled native range input, so arrow keys, Home/End, touch dragging and form submission all work without JavaScript.

src/components/ui/slider.tsx

About

From the component's own source, where it was written.

A native <input type="range">, restyled.

The input keeps every behaviour that makes a range input worth using — arrow keys, Home/End, touch dragging, form submission, the announced role — and paints none of it. Track, fill and thumb are the three elements beside it, and the input sits transparently on top as the hit target.

**Why not just style the pseudo-elements.** A native thumb's position is derived from value by the browser, and there is no property on it that can be transitioned, so it can only ever snap. The thumb here is positioned from --slider-progress, a registered custom property, which means it can be interpolated — and so it trails the pointer slightly instead of being welded to it. The fill reads the same variable, so the two can never drift apart.

The vendor pseudo-elements still matter for one thing: a range input insets the thumb's travel by half its own width at each end, and the maths below has to match. index.css keeps their size and strips their borders so the two agree exactly.

**Three colours are variables, not classes**, alongside the sizes that already were:

--slider-rail the unfilled track (default --secondary) --slider-fill the filled part (default --foreground) --slider-knob the thumb's ring (default --foreground)

They exist so this component can be reused where the page tokens are wrong rather than reimplemented there. A player's chrome is black in both themes, so --foreground is invisible on it; a waveform scrubber wants no rail or fill at all because the peaks behind are the track. Both were hand-rolled range inputs until these existed, and both looked like a different product.

Install

npx astralyx-ui add slider

Usage

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

<Slider min={0} max={100} step={5} defaultValue={40} showValue />

Composer

Change the props and the source below follows.

50
Props
tsx
<Slider
  label="Volume"
  showValue
/>

Examples

Sizes

With value

Stepped and disabled

API

Generated from the source, so it cannot fall behind the code. One table per exported component, then hooks, functions and types.

Slider

Extends Omit<ComponentProps<'input'>, 'size' | 'type'>.

PropTypeDefaultDescription
size'sm' | 'default' | 'lg''default'Track thickness and thumb size, passed to the CSS as variables.
labelstring—Accessible name. A range input without one is announced as just "slider" — pass this or an aria-labelledby.
showValuebooleanfalseRender the current value beside the track.
formatValue(value: number) => stringStringFormat the displayed value — units, percentages, currency.
minnumber—Standard range attributes. The fill and the thumb offset are derived from them.
maxnumber—Standard range attributes. The fill and the thumb offset are derived from them.
stepnumber—Standard range attributes. The fill and the thumb offset are derived from them.

Functions

FunctionSignatureDescription
sliderVariants(props?: { size?: 'sm' | 'default' | 'lg' }) => stringA native <input type="range">, restyled.

Behaviour

TopicDetailDescription
the thumbnot the native oneTrack, fill and thumb are real elements; the input sits transparently on top and keeps every native behaviour. A native thumb's position is derived from value by the browser with no property to transition, so it can only snap — this one is offset from a registered custom property and eases to its value in 130ms, trailing the pointer instead of being welded to it.