Gecko UIGecko UI

Counter Input

A numeric input component with increment/decrement buttons

Counter Input

A numeric input component with increment and decrement buttons. Supports controlled mode, min/max boundaries, custom step values, and multiple sizes.

Installation

import { CounterInput } from "@geckoui/geckoui";

Basic Usage

import { useState } from "react";

function Example() {
  const [value, setValue] = useState("0");

  return <CounterInput value={value} onChange={setValue} />;
}

Props API

PropTypeDefaultDescription
valuestring-Current value as a string (required)
onChange(value: string) => void-Callback with the sanitised string (required)
minnumber-InfinityMinimum allowed value
maxnumberInfinityMaximum allowed value
stepnumber1Increment/decrement step
size'sm' | 'md' | 'lg''md'Size variant
disabledbooleanfalseDisable the entire component
readOnlybooleanfalseRead-only mode - buttons and input are non-interactive
allowTypingbooleanfalseAllow typing in input field
inputClassNamestring-CSS class for the input element
buttonClassNamestring-CSS class for the buttons
strictbooleantrueDrop leading zeros and normalise the whole part
positiveOnlybooleanfalseReject a leading minus sign
maxFractionDigitsnumber-Digits kept after the decimal point. 0 makes it integer only
maxWholeDigitPlacesnumber-Digits kept before the decimal point
classNamestring-CSS class for the container

Examples

Sizes

<CounterInput value={value} onChange={setValue} size="sm" />
<CounterInput value={value} onChange={setValue} size="md" />
<CounterInput value={value} onChange={setValue} size="lg" />

Min/Max Boundaries

Buttons are automatically disabled when reaching the min or max value.

<CounterInput value={value} onChange={setValue} min={0} max={10} />

Custom Step

<CounterInput value={value} onChange={setValue} step={5} />

Editable Input

When allowTyping is true, users can type directly into the input field. Only digits, one decimal point and a leading minus sign are accepted.

Why the value is a string

The value is held as a string so a half-typed number survives. If it were a number, typing 2.8 and then deleting the 8 would leave "2.", which parseFloat collapses to 2 — deleting the decimal point as you type, so you could never reach 2.5.

Convert when you need a number:

const [amount, setAmount] = useState("0");
const total = Number(amount || 0) * price;

The value is clamped to min and max on blur and by the buttons, not on every keystroke, so typing 1 on the way to 10 is not snapped to the minimum.

<CounterInput value={value} onChange={setValue} allowTyping />

Disabled State

<CounterInput value={value} onChange={setValue} disabled />

Read Only State

In read-only mode, buttons and input are non-interactive but maintain normal appearance (no dimmed opacity).

<CounterInput value={value} onChange={setValue} readOnly />

Module Augmentation

The CounterInput component supports TypeScript module augmentation, allowing you to add custom sizes with full type safety.

Live Example

A custom xl size created using module augmentation:

Step 1: TypeScript Declaration

Create a declaration file (e.g., gecko.d.ts) in your project:

import "@geckoui/geckoui";

declare module "@geckoui/geckoui" {
  interface CounterInputSizeMap {
    xl: unknown;
  }
}

Step 2: Add CSS Styles

Add the corresponding styles to your global CSS/SCSS file:

.GeckoUICounterInput[data-size="xl"] .GeckoUICounterInput__button {
  @apply h-14 w-14;
}

.GeckoUICounterInput[data-size="xl"] .GeckoUICounterInput__input {
  @apply h-14 w-24 text-lg;
}

.GeckoUICounterInput[data-size="xl"] .GeckoUICounterInput__icon {
  @apply h-6 w-6;
}

Step 3: Use Your Custom Size

<CounterInput value={value} onChange={setValue} size="xl" />

Accessibility

  • Buttons have aria-label for screen readers ("Increment" and "Decrement")
  • Input uses inputMode="numeric" for appropriate mobile keyboards
  • Focus states are clearly visible
  • Disabled states are properly communicated

Styling

The component uses BEM base class names with data-* attributes for modifiers:

  • GeckoUICounterInput - Main container
  • GeckoUICounterInput[data-size="sm|md|lg"] - Size modifiers
  • GeckoUICounterInput[data-state="disabled"] - Disabled state
  • GeckoUICounterInput[data-state="readonly"] - Read-only state
  • GeckoUICounterInput__button - Increment/decrement buttons
  • GeckoUICounterInput__button[data-action="increment"] - Plus button
  • GeckoUICounterInput__button[data-action="decrement"] - Minus button
  • GeckoUICounterInput__input - The numeric input field
  • GeckoUICounterInput__icon - Button icons
  • GeckoUICounterInput__icon[data-icon="minus"] - Minus icon
  • GeckoUICounterInput__icon[data-icon="plus"] - Plus icon