Rango de precio
Dos deslizadores para el precio mínimo y máximo, con un pequeño histograma que muestra dónde se agrupan los precios.
Ajustes
Código
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="Precio mínimo"
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="Precio máximo"
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 para IA
Pégalo en ChatGPT, Claude o Cursor y adaptará el bloque a tu proyecto con estos ajustes, aunque no uses React.
Más en Inputs
Dónde usarlo
Un filtro de precio con histograma enseña dónde está la mayoría de productos antes de mover nada. Lo ves en webs de alquiler vacacional, portales de coches de ocasión y tiendas grandes. Los dos deslizadores nunca se cruzan. Si tu catálogo tiene barra de filtros, este slider de rango de precio encaja ahí sin más.
Las barras de la demo son de ejemplo, así que aliméntalas con la distribución real de tus precios o confundirán a la gente. Ajusta la altura del histograma a tu barra lateral y cambia el símbolo de moneda si no vendes en euros.