AniUI

Stepper

Numeric increment/decrement control with min, max, and step support. Ideal for quantity selectors.

1
Web preview — components render natively on iOS & Android
import { Stepper } from "@/components/ui/stepper";

const [count, setCount] = useState(1);
<Stepper value={count} onChange={setCount} min={0} max={10} />

Installation#

npx @aniui/cli add stepper

Sizes#

1
1
1
Web preview — components render natively on iOS & Android
<Stepper size="sm" value={1} onChange={setCount} />
<Stepper size="md" value={1} onChange={setCount} />
<Stepper size="lg" value={1} onChange={setCount} />

Custom Step#

Use the step prop to control the increment/decrement amount.

0
Step: 5
Web preview — components render natively on iOS & Android
<Stepper value={0} onChange={setCount} min={0} max={100} step={5} />

Props#

PropTypeDefault
value
number
onChange
(value: number) => void
min
number
0
max
number
99
step
number
1
size
"sm" | "md" | "lg"
"md"
className
string

Accessibility#

  • accessibilityRole="adjustable" with increment/decrement buttons.
  • Current value is announced to screen readers via accessibilityValue.

Source#

components/ui/stepper.tsx
import React from "react";
import { View, Text, Pressable } from "react-native";
import { cva, type VariantProps } from "class-variance-authority";
import { Minus, Plus } from "lucide-react-native";
import { cn } from "@/lib/utils";
import { useThemeColors } from "@/components/ui/theme-provider";

const stepperVariants = cva("flex-row items-center self-start rounded-lg border border-border", {
  variants: {
    size: {
      sm: "h-9",
      md: "h-12",
      lg: "h-14",
    },
  },
  defaultVariants: { size: "md" },
});

const buttonVariants = cva("items-center justify-center border-border", {
  variants: {
    size: {
      sm: "w-9",
      md: "w-12",
      lg: "w-14",
    },
  },
  defaultVariants: { size: "md" },
});

export interface StepperProps
  extends React.ComponentPropsWithoutRef<typeof View>,
    VariantProps<typeof stepperVariants> {
  className?: string;
  value: number;
  onChange: (value: number) => void;
  min?: number;
  max?: number;
  step?: number;
}

export function Stepper({ size, className, value, onChange, min = 0, max = 99, step = 1, ...props }: StepperProps) {
  const colors = useThemeColors();
  const fg = colors.foreground;
  const canDec = value - step >= min;
  const canInc = value + step <= max;

  return (
    <View
      className={cn(stepperVariants({ size }), className)}
      accessibilityRole="adjustable"
      accessibilityValue={{ min, max, now: value }}
      {...props}
    >
      <Pressable
        className={cn(buttonVariants({ size }), "h-full border-e", !canDec && "opacity-30")}
        onPress={() => { if (canDec) onChange(value - step); }}
        disabled={!canDec}
        accessible={true}
        accessibilityRole="button"
        accessibilityLabel="Decrease"
      >
        <Minus size={18} color={fg} />
      </Pressable>
      <View className="w-14 items-center justify-center">
        <Text className="text-base font-medium text-foreground">{value}</Text>
      </View>
      <Pressable
        className={cn(buttonVariants({ size }), "h-full border-s", !canInc && "opacity-30")}
        onPress={() => { if (canInc) onChange(value + step); }}
        disabled={!canInc}
        accessible={true}
        accessibilityRole="button"
        accessibilityLabel="Increase"
      >
        <Plus size={18} color={fg} />
      </Pressable>
    </View>
  );
}