Skip to content
v1.0.3

Slider

A drag- and keyboard-driven numeric input. Single value by default, or a two-thumb min–max band with range. Supports steps, tick marks, a value readout, and full keyboard control (arrows, Page Up/Down, Home/End).

The slider's drag/keyboard behavior is JavaScript-driven. With React/Vue it just works. For plain HTML, include the behavior layer: it reads data-min/data-max/data-step on the root and data-value on each thumb (two thumbs = a range), wires up drag + keyboard, and emits a jl-slider:change event on the root.

bash
jlds add slider

Usage

html
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/jarooda/jlds@main/registry/css/index.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/jarooda/jlds@main/registry/css/slider.css">
<!-- behavior layer: wires up drag + keyboard -->
<script src="https://cdn.jsdelivr.net/gh/jarooda/jlds@main/registry/js/core.js" defer></script>
<script src="https://cdn.jsdelivr.net/gh/jarooda/jlds@main/registry/js/slider.js" defer></script>

<!-- the script reads data-* and positions the fill/thumb -->
<div class="jl-slider jl-slider--md" data-min="0" data-max="100" data-step="1">
  <div class="jl-slider__track">
    <span class="jl-slider__fill"></span>
    <button type="button" class="jl-slider__thumb" data-value="40"
      role="slider" aria-valuemin="0" aria-valuemax="100" aria-valuenow="40" tabindex="0"></button>
  </div>
</div>
vue
<script setup lang="ts">
import { ref } from "vue"
import { Slider } from "@/components/ui/slider"

const volume = ref(40)
</script>

<template>
  <Slider v-model="volume" />
</template>
tsx
import { useState } from "react"
import { Slider } from "@/components/ui/slider"

const [volume, setVolume] = useState(40)

<Slider value={volume} onChange={(v) => setVolume(v as number)} />

Label & value

Add a label and/or showValue for a header row. Use formatValue to format the readout.

html
<div class="jl-slider jl-slider--md" data-min="0" data-max="100" data-step="1" data-suffix="%">
  <div class="jl-slider__head">
    <span class="jl-slider__label">Volume</span>
    <span class="jl-slider__value">65%</span>
  </div>
  <div class="jl-slider__track">
    <span class="jl-slider__fill"></span>
    <button type="button" class="jl-slider__thumb" data-value="65" role="slider"
      aria-valuemin="0" aria-valuemax="100" aria-valuenow="65" tabindex="0"></button>
  </div>
</div>
vue
<template>
  <Slider
    v-model="volume"
    label="Volume"
    show-value
    :format-value="(v) => `${v}%`"
  />
</template>
tsx
<Slider
  value={volume}
  onChange={(v) => setVolume(v as number)}
  label="Volume"
  showValue
  formatValue={(v) => `${v}%`}
/>

Range & marks

Set range for a two-thumb band (value is [min, max]), and marks for tick labels.

html
<div class="jl-slider jl-slider--md" data-min="0" data-max="100" data-step="1">
  <div class="jl-slider__track">
    <span class="jl-slider__fill"></span>
    <button type="button" class="jl-slider__thumb" data-value="25" role="slider"
      aria-valuemin="0" aria-valuemax="100" aria-valuenow="25" tabindex="0"></button>
    <button type="button" class="jl-slider__thumb" data-value="75" role="slider"
      aria-valuemin="0" aria-valuemax="100" aria-valuenow="75" tabindex="0"></button>
  </div>
  <div class="jl-slider__marks">
    <span class="jl-slider__mark" style="left: 0%">0</span>
    <span class="jl-slider__mark" style="left: 50%">50</span>
    <span class="jl-slider__mark" style="left: 100%">100</span>
  </div>
</div>
vue
<template>
  <Slider
    v-model="priceRange"
    range
    :marks="[0, 50, 100]"
  />
</template>
tsx
const [priceRange, setPriceRange] = useState<[number, number]>([25, 75])

<Slider
  range
  value={priceRange}
  onChange={(v) => setPriceRange(v as [number, number])}
  marks={[0, 50, 100]}
/>

Props

Slider is controlled (value + onChange) or uncontrolled (defaultValue). The value is a number, or [number, number] when range.

React

PropTypeDefaultDescription
min / maxnumber0 / 100Bounds
stepnumber1Step increment
valuenumber | [number, number]Controlled value
defaultValuenumber | [number, number]Uncontrolled initial value
rangebooleanfalseTwo-thumb min–max band
onChange(value) => voidFires on every drag/key change
onChangeEnd(value) => voidFires once at the end of a drag / on key commit
disabledbooleanfalseDisable interaction
size"sm" | "md" | "lg""md"Track/thumb scale
marks(number | { value, label })[]Tick labels under the track
showValuebooleanfalseShow the value in the header
formatValue(v: number) => ReactNodev => vFormat the readout
labelReact.ReactNodeHeader label

Vue

Same options. Use v-model for the value and @change-end for the commit event; format-value takes a (v: number) => string \| number function.

CSS classes (HTML)

ClassPurpose
.jl-sliderWrapper — always required
.jl-slider--sm / --md / --lgScale
.jl-slider--disabledDimmed, non-interactive
.jl-slider__head / __label / __valueOptional header row
.jl-slider__trackThe rail (attach pointer handlers here)
.jl-slider__fillThe filled portion (set left/width or left/right for range)
.jl-slider__thumbA draggable thumb (role="slider" + aria-value*)
.jl-slider__marks / .jl-slider__markTick label row

HTML data attributes (behavior layer)

With the behavior layer loaded, the script reads these and positions/updates everything — you don't set the fill/thumb left/width yourself.

AttributeOnDefaultPurpose
data-min / data-max / data-step.jl-slider0 / 100 / 1Range and increment
data-suffix.jl-sliderAppended to the __value readout (e.g. %)
data-value.jl-slider__thumbminA thumb's initial value (two thumbs ⇒ range)

It emits a jl-slider:change event on the root: el.detail.value is a number, or [min, max] for a range.