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
| Prop | Type | Default | Description |
|---|---|---|---|
value | string | - | Current value as a string (required) |
onChange | (value: string) => void | - | Callback with the sanitised string (required) |
min | number | -Infinity | Minimum allowed value |
max | number | Infinity | Maximum allowed value |
step | number | 1 | Increment/decrement step |
size | 'sm' | 'md' | 'lg' | 'md' | Size variant |
disabled | boolean | false | Disable the entire component |
readOnly | boolean | false | Read-only mode - buttons and input are non-interactive |
allowTyping | boolean | false | Allow typing in input field |
inputClassName | string | - | CSS class for the input element |
buttonClassName | string | - | CSS class for the buttons |
strict | boolean | true | Drop leading zeros and normalise the whole part |
positiveOnly | boolean | false | Reject a leading minus sign |
maxFractionDigits | number | - | Digits kept after the decimal point. 0 makes it integer only |
maxWholeDigitPlaces | number | - | Digits kept before the decimal point |
className | string | - | 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-labelfor 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 containerGeckoUICounterInput[data-size="sm|md|lg"]- Size modifiersGeckoUICounterInput[data-state="disabled"]- Disabled stateGeckoUICounterInput[data-state="readonly"]- Read-only stateGeckoUICounterInput__button- Increment/decrement buttonsGeckoUICounterInput__button[data-action="increment"]- Plus buttonGeckoUICounterInput__button[data-action="decrement"]- Minus buttonGeckoUICounterInput__input- The numeric input fieldGeckoUICounterInput__icon- Button iconsGeckoUICounterInput__icon[data-icon="minus"]- Minus iconGeckoUICounterInput__icon[data-icon="plus"]- Plus icon
Related Components
- Input - Text input component
- RHFCounterInput - React Hook Form integration for this component