Increment
A numeric stepper with minus/plus buttons and a numeric text field.
Increment is a numeric stepper that composes the existing Button and Input components: a minus button, a centered numeric field, and a plus button laid out in a row. Its root renders through Container, a wrapper around Roblox's native Frame.
Import
import { Increment } from "@rbxts/react-clean-ui";Basic usage
The value prop sets the initial value. onChange is called whenever the value changes, whether from clicking the minus/plus buttons or typing directly into the field.
Step
Use step to control how much the minus and plus buttons change the value by. It defaults to 1.
<Increment value={20} step={5} />Clicking the plus button changes the value from 20 to 25, then 30, and so on.
Minimum and maximum
Use min and max to bound the value.
<Increment value={0} min={0} max={10} />The minus and plus buttons clamp the value to [min, max]. If the value is already at min, clicking minus does nothing; if it's already at max, clicking plus does nothing — no state change and no onChange call.
min and max are not enforced while typing into the field. Instead, they're checked when the field loses focus (the same behavior as Input's own min/max): if the typed value falls outside the range, it's clamped to the nearest bound and onChange fires again with the corrected value.
Controlled value
Set controlled when the rendered value should always come from the value prop, mirroring Input's controlled/uncontrolled convention.
function ControlledIncrement() {
const [value, setValue] = React.useState(5);
return (
<Increment
controlled
value={value}
min={0}
max={10}
onChange={setValue}
/>
);
}Without controlled, value is used as the initial value and Increment manages subsequent changes internally.
Intents
intent is forwarded to the minus and plus buttons, controlling their background, border, and icon colors. It has no effect on the numeric field, since Input has no intent prop.
<Increment value={5} intent="success" />
<Increment value={5} intent="danger" />Scaling
Increment supports the shared scale prop, which is forwarded to the minus/plus buttons and the numeric field, resolving their typography and icon size from the active theme.
<Increment value={5} scale="sm" />
<Increment value={5} scale="lg" />Available scales are xs, sm, md, lg, and xl.
Spacing
The shared spacing prop controls the gap between the minus button, field, and plus button, as well as each part's own internal padding.
<Increment value={5} spacing="lg" />Props
Increment-specific props
| Prop | Type | Default | Description |
|---|---|---|---|
value | number | Required | The current value, or the initial value when uncontrolled. |
onChange | (value: number) => void | undefined | Called whenever the value changes, from a button click or a typed edit. |
step | number | 1 | Amount the minus/plus buttons change the value by. |
min | number | undefined | Inclusive lower bound, enforced by the minus button and on blur of the field. |
max | number | undefined | Inclusive upper bound, enforced by the plus button and on blur of the field. |
controlled | boolean | false | Reads the rendered value directly from value, instead of managing it internally. |
Shared props
The increment also supports props inherited from the following interfaces.
| Interface | Purpose |
|---|---|
IntentElementProps | Forwards intent to the minus/plus Buttons. Has no effect on the numeric field. |
SpacedElementProps | Configures the gap between the three parts and each part's own internal padding. |
ScalableElementProps | Configures the scale forwarded to the minus/plus Buttons and the Input. |
React.InstanceProps<ImageLabel> | Accepts native ImageLabel properties on the root Container. |
Behaviour
- The root renders through
Containerat a fixedSize={UDim2.fromScale(1, 0)}withAutomaticSize.Y— full available width, height fit to content.Size/AutomaticSizepassed in props are not honored. Incrementmanages its own numeric state (separate fromInput's internal string state) unlesscontrolledis set. TheInputis always renderedcontrolled={true}.- Activating the minus/plus
Buttoncomputescurrent - step/current + step, clamps it to[min, max], updates internal state (unlesscontrolled), and callsonChangewith the clamped value. - If the value is already at
min(minus) ormax(plus), activating that button is a no-op — neither internal state noronChangefire. - Typing into the field is forwarded straight through as a number without clamping mid-edit. Only the field's
FocusLosthandling (inherited fromInput) clamps an out-of-range value once focus is lost, at which pointonChangefires again with the corrected value. - The minus/plus buttons are rendered with
disabledset once the value reachesmin/max, so they also switch to the intent'sdisabledcolours (when defined in the theme), in addition to their activation being guarded.
Theme values
theme.components.increment only exposes a button override, which is forwarded as styleOverride to the minus/plus Buttons. Otherwise, Increment's appearance is driven by:
theme.components.increment.button— overrides theme values (colours, corners, border, etc.) for the minus/plusButtons only.theme.components.button— used by the minus and plusButtons when notheme.components.increment.buttonoverride is set.theme.components.input— used by the numeric field.
The gap between the three parts falls back to theme.default.spacing when spacing is omitted, the same default HStack and Button use elsewhere.