2
React + MotionInputs

Quantity stepper

Minus and plus buttons around a number that slides up or down each time it changes.

Settings

2
12

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.