Skip to content

QuantityStepper

npm package: import it from 'flootlets'
bun add flootlets
Or copy it in with the shadcn CLI: import it from '@/components/ui/…'
npx shadcn@latest add https://dyeoh.github.io/flootlets/r/quantity-stepper.json

Both need Tailwind CSS v4; see Installation for the setup.

5 left
RM 12.90
import { createSignal } from 'solid-js';
// Only 5 left in stock, so + stops at 5.
export default function StepperExample() {
const [qty, setQty] = createSignal(1);
return (
<Cluster gap={6} align="end">
<QuantityStepper
label="Quantity"
value={qty()}
onChange={setQty}
max={5}
description="5 left"
/>
<Price amount={{ amount: 1290 * qty(), currency: 'MYR' }} locale="en-MY" size="lg" />
</Cluster>
);
}

Built on Kobalte’s NumberField. The number is a spinbutton: arrow keys step it, Page Up/Down step further, Home/End jump to the limits, and you can type a number. The − and + buttons switch off at min and max. When − or + is pressed, the new quantity is announced to screen readers (focus is on the button then, so the field’s own announcement wouldn’t be heard).

min defaults to 1: taking an item out of the cart should be a separate, clearly labelled “Remove” button, not a quantity of 0. Set max to the stock left; the gnerkulfloot API still checks stock when the order is placed.

The field is only as wide as its content (the stepper, or a longer label or description), so it lines up with the stepper instead of stretching across a column. Pass class="w-full" to stretch it.

With disabled, e.g. when the item is sold out, the whole stepper dims, the number with it, and shows the not-allowed cursor, and nothing steps.

Sold out
// Sold out: the whole stepper dims, number included, and nothing steps.
export default function StepperDisabled() {
return <QuantityStepper label="Quantity" defaultValue={1} disabled description="Sold out" />;
}
Prop Type
label string Required, e.g. “Quantity”. Use hideLabel in a compact cart row
value / defaultValue / onChange number / number / (value: number) => void onChange runs only when the value really changes, never on first render
min, max, step number Defaults 1, none, 1
size 'sm' | 'default'
labels { decrement?: string; increment?: string } Button labels for translation. Default “Decrease quantity” / “Increase quantity”
description, error, name, required, disabled

flootlets 0.2.3 · Changelog