Price range slider
Two sliders for a minimum and maximum price, with a small histogram above showing where prices cluster.
Settings
Code
import { useState } from "react";
import { motion } from "framer-motion";
const spring = { type: "spring" as const, stiffness: 300, damping: 30 };
const bars = [5, 8, 13, 20, 28, 35, 42, 50, 46, 38, 31, 23, 16, 10];
const rangeClass = [
"h-1 w-full appearance-none rounded-full bg-black/5 outline-none dark:bg-white/10",
"[&::-webkit-slider-thumb]:h-[22px] [&::-webkit-slider-thumb]:w-[22px] [&::-webkit-slider-thumb]:cursor-pointer [&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:bg-white [&::-webkit-slider-thumb]:shadow-[0_1px_4px_rgba(0,0,0,0.25),0_0_0_0.5px_rgba(0,0,0,0.06)]",
"[&::-moz-range-thumb]:h-[22px] [&::-moz-range-thumb]:w-[22px] [&::-moz-range-thumb]:cursor-pointer [&::-moz-range-thumb]:rounded-full [&::-moz-range-thumb]:border-none [&::-moz-range-thumb]:bg-white [&::-moz-range-thumb]:shadow-[0_1px_4px_rgba(0,0,0,0.25)]",
].join(" ");
interface PriceRangeProps {
value?: [number, number];
defaultValue?: [number, number];
onChange?: (value: [number, number]) => void;
height?: number;
}
export default function PriceRange({
value,
defaultValue = [150, 750],
onChange,
height = 1,
}: PriceRangeProps) {
const [internal, setInternal] = useState<[number, number]>(defaultValue);
const [a, b] = value ?? internal;
const set = (next: [number, number]) => {
if (value === undefined) setInternal(next);
onChange?.(next);
};
return (
<div className="w-72 rounded-[22px] bg-white p-5 text-[#1d1d1f] shadow-[0_1px_2px_rgba(0,0,0,0.03),0_8px_28px_rgba(0,0,0,0.05)] dark:bg-[#1c1c1e] dark:text-[#f5f5f7]">
<div className="mb-3 flex h-16 items-end gap-1">
{bars.map((h, i) => (
<motion.i
key={i}
className="flex-1 rounded-t bg-[#0071e3]/30"
animate={{ height: h * height }}
transition={{ ...spring, delay: i * 0.02 }}
/>
))}
</div>
<div className="space-y-2">
<input
aria-label="Min price"
className={rangeClass}
type="range"
min="0"
max="1000"
value={a}
onChange={(e) => set([Math.min(Number(e.target.value), b - 25), b])}
/>
<input
aria-label="Max price"
className={rangeClass}
type="range"
min="0"
max="1000"
value={b}
onChange={(e) => set([a, Math.max(Number(e.target.value), a + 25)])}
/>
</div>
<div className="mt-3 flex justify-between text-sm">
<span>{a} €</span>
<span>{b} €</span>
</div>
</div>
);
}
Prompt for AI
Paste it into ChatGPT, Claude or Cursor and it will adapt the block to your project with these settings, even if you don’t use React.
More in Inputs
Where to use it
A price filter with a histogram tells shoppers where most products sit before they drag anything. It is what you see on holiday rental sites, used car listings and bigger online shops. The two sliders never cross, and the prices update as you move them. If your catalog has a filter sidebar, this price range slider slots right into it.
The bars in the demo are sample data, so feed them your real price spread or they will mislead people. Adjust the histogram height to fit your sidebar, and change the currency sign if you do not sell in euros.