Skip to main content

createSlider

Headless pointer dragging, keyboard stepping, step snapping, and thumb styles for single or multi-thumb range sliders.

View as Markdown

createSlider is the headless interaction engine behind Moraine’s Slider component. It provides pointer capture dragging, keyboard arrow stepping, value clamping, step snapping, and thumb/range inline style calculations.

Import#

import { createSlider } from 'moraine/utils'

Features#

  • Single & Multi-thumb: Supports numeric values or tuples ([min, max]) for dual-thumb range selection.
  • Pointer Capture: Smooth pointer tracking that prevents cursor escape during fast mouse or touch gestures.
  • Keyboard Stepping: Handles Arrow Left/Right/Up/Down, PageUp/PageDown, and Home/End.
  • Orientation & Inversion: Supports horizontal or vertical orientation, as well as inverted axes and RTL.

Signature#

function createSlider<TValue extends number | number[]>(
  props: CreateSliderProps<TValue>,
  options?: CreateSliderOptions<TValue>,
): CreateSliderReturn<TValue>

Usage Example#

src/CustomSlider.tsx
import { createSlider } from 'moraine/utils'

export function CustomSlider() {
  const slider = createSlider({
    min: 0,
    max: 100,
    step: 5,
    defaultValue: 50,
  })

  return (
    <div class="relative h-6">
      <div
        ref={(element) => slider.setTrackRef(element)}
        class="absolute inset-x-0 top-1/2 h-2 -translate-y-1/2 rounded-full bg-muted touch-none"
        onPointerDown={slider.onTrackPointerDown}
        onPointerMove={slider.onTrackPointerMove}
        onPointerUp={slider.onTrackPointerUp}
        onPointerCancel={slider.onPointerCancel}
        onLostPointerCapture={slider.onPointerCancel}
      >
        <div class="absolute h-full rounded-full bg-primary" style={slider.rangeStyle()} />
      </div>

      <div
        ref={(element) => slider.setThumbRefs(() => [element])}
        tabIndex={0}
        role="slider"
        aria-label="Amount"
        aria-valuenow={slider.currentValues()[0] ?? 0}
        aria-valuemin={0}
        aria-valuemax={100}
        onPointerDown={(event) => slider.onThumbPointerDown(0, event)}
        onPointerMove={slider.onThumbPointerMove}
        onPointerUp={slider.onThumbPointerUp}
        onPointerCancel={slider.onPointerCancel}
        onLostPointerCapture={slider.onPointerCancel}
        onKeyDown={(event) => slider.onThumbKeyDown(0, event)}
        onKeyUp={slider.onThumbKeyUp}
        style={slider.thumbStyles()[0]}
        class="absolute top-1/2 size-5 -translate-x-1/2 -translate-y-1/2 rounded-full bg-primary ring-2 ring-background shadow-md touch-none"
      />
    </div>
  )
}