150 €750 €
React + MotionInputs

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

150€
750€
1

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.