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.jsonUsage
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
| Prop | Type | Default | Description |
|---|---|---|---|
| label | string | required | Label shown at the start of the track. |
| value | [number, number] | undefined | Controlled [min, max] value. Use together with onValueChange. |
| defaultValue | [number, number] | [min, max] | Initial [min, max] value for uncontrolled mode. |
| onValueChange | (value: [number, number]) => void | — | Called with the new [min, max] on drag, click, or key press. |
| min | number | 0 | Minimum value. |
| max | number | 100 | Maximum value. |
| step | number | 1 | Smallest increment. |
| formatValue | (value: number) => string | value.toFixed(...) | Format each displayed value, based on step's decimal precision by default. |
| minThumbLabel | string | "Minimum" | Accessible name for the lower thumb. |
| maxThumbLabel | string | "Maximum" | Accessible name for the upper thumb. |