Quantity stepper
Minus and plus buttons around a number that slides up or down each time it changes.
Settings
Code
import { useState } from "react";
import { AnimatePresence, motion } from "framer-motion";
import { Minus, Plus } from "lucide-react";
interface AnimatedQuantityProps {
value?: number;
defaultValue?: number;
max?: number;
onChange?: (value: number) => void;
}
export default function AnimatedQuantity({
value,
defaultValue = 2,
max = 12,
onChange,
}: AnimatedQuantityProps) {
const [internal, setInternal] = useState(defaultValue);
const n = value ?? internal;
const set = (next: number) => {
if (value === undefined) setInternal(next);
onChange?.(next);
};
return (
<div className="flex items-center gap-5 rounded-full bg-white p-2 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]">
<button
type="button"
aria-label="Decrease"
onClick={() => set(Math.max(0, n - 1))}
className="grid h-9 w-9 place-items-center rounded-full bg-black/5 dark:bg-white/10"
>
<Minus className="h-4 w-4" />
</button>
<AnimatePresence mode="popLayout" initial={false}>
<motion.strong
key={n}
initial={{ y: 12, opacity: 0 }}
animate={{ y: 0, opacity: 1 }}
exit={{ y: -12, opacity: 0 }}
className="w-7 text-center"
>
{n}
</motion.strong>
</AnimatePresence>
<button
type="button"
aria-label="Add"
onClick={() => set(Math.min(max, n + 1))}
className="grid h-9 w-9 place-items-center rounded-full bg-black/5 dark:bg-white/10"
>
<Plus className="h-4 w-4" />
</button>
</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 quantity picker shows up wherever people order things: the cart of an online shop, concert tickets, portions in a food delivery app or rooms in a hotel booking. The sliding number makes each tap visible, so nobody adds three bottles of wine by accident. A quantity stepper sounds boring, but people touch it more than almost anything.
Set a sensible maximum for your case, like your real stock or the most tickets per order. It stops at zero by default; if zero makes no sense, for example in a cart line, start the minimum at one instead.