Elastic Range Slider

A single-track, dual-thumb range slider — a min and a max handle sharing one track, for things like price filters. Built in the same visual language as Elastic Slider.

Last updated August 7, 2026

CreditsModifiedPublished

Copied from Josh Puckett — Interface Craft and Chánh Đại — Elastic Slider.

  • New component, not part of the original Elastic Slider — extracted from a single-value slider into its own dual-thumb primitive, with the same spring-animated drag, rubber-band overshoot, and step-snap on release
  • Positioning stays physically left-to-right in both LTR and RTL — the left handle always controls the left edge and the right handle always controls the right edge, so drag direction never flips under RTL
  • Each thumb clamps against the other's current value, so min can never be dragged past max or vice versa
  • Same hover/active decile hash marks as Elastic Slider, positioned physically so they line up with the track regardless of direction

Overview

"use client"
 
import { useState } from "react"
 
import { ElasticRangeSlider } from "@/components/ui/elastic-range-slider"
 
export function ElasticSliderPriceRangeExample() {
  const [range, setRange] = useState<[number, number]>([200, 700])
 
  return (
    <div className="w-64">
      <ElasticRangeSlider
        label="Price"
        min={0}
        max={1000}
        step={10}
        value={range}
        onValueChange={setRange}
        formatValue={(v) => `$${v}`}
        minThumbLabel="Min price"
        maxThumbLabel="Max price"
      />
    </div>
  )
}

Installation

$ npx shadcn@latest add https://ui.persian-labs.ir/r/elastic-range-slider.json

Usage

import { ElasticRangeSlider } from "@/components/elastic-range-slider"
 
const [range, setRange] = useState<[number, number]>([200, 700])
 
<ElasticRangeSlider
  label="Price"
  min={0}
  max={1000}
  step={10}
  value={range}
  onValueChange={setRange}
  formatValue={(v) => `$${v}`}
/>

Toman price range

A Persian-market pattern: Toman pricing with Persian digits, and the selected range spelled out below the track as از … تومان / تا … تومان.

از 1٬000٬000 تومانتا 5٬000٬000 تومان
"use client"
 
import { useState } from "react"
 
import { ElasticRangeSlider } from "@/components/ui/elastic-range-slider"
 
function formatToman(value: number) {
  return value.toLocaleString("en-US").replaceAll(",", "٬")
}
 
export function ElasticSliderRangeTomanExample() {
  const [range, setRange] = useState<[number, number]>([1_000_000, 5_000_000])
  const [min, max] = range
 
  return (
    <div className="w-64">
      <ElasticRangeSlider
        label="قیمت"
        min={0}
        max={10_000_000}
        step={100_000}
        value={range}
        onValueChange={setRange}
        formatValue={(v) => formatToman(v)}
        minThumbLabel="حداقل قیمت"
        maxThumbLabel="حداکثر قیمت"
      />
      <div className="mt-3 flex flex-col gap-1 text-sm text-muted-foreground">
        <span>از {formatToman(min)} تومان</span>
        <span>تا {formatToman(max)} تومان</span>
      </div>
    </div>
  )
}

Percent range

"use client"
 
import { ElasticRangeSlider } from "@/components/ui/elastic-range-slider"
 
export function ElasticSliderRangePercentExample() {
  return (
    <div className="w-64">
      <ElasticRangeSlider
        label="Discount"
        min={0}
        max={100}
        step={5}
        defaultValue={[20, 60]}
        formatValue={(v) => `${v}%`}
        minThumbLabel="Min discount"
        maxThumbLabel="Max discount"
      />
    </div>
  )
}

API Reference

ElasticRangeSlider

PropTypeDefaultDescription
labelstringrequiredLabel shown at the start of the track.
value[number, number]undefinedControlled [min, max] value. Use together with onValueChange.
defaultValue[number, number][min, max]Initial [min, max] value for uncontrolled mode.
onValueChange(value: [number, number]) => voidCalled with the new [min, max] on drag, click, or key press.
minnumber0Minimum value.
maxnumber100Maximum value.
stepnumber1Smallest increment.
formatValue(value: number) => stringvalue.toFixed(...)Format each displayed value, based on step's decimal precision by default.
minThumbLabelstring"Minimum"Accessible name for the lower thumb.
maxThumbLabelstring"Maximum"Accessible name for the upper thumb.