Input Number
Accessible numeric input with stepping controls, bounds, adornments, and validation.
InputNumber is a numeric form control for integer and decimal values. It keeps text editing flexible while providing increment and decrement buttons, keyboard stepping, bounds, formatting, adornments, and the same field states as the other Mosmox UI inputs.
Basic usage
import { InputNumber } from '@mosmox/ui';
import { Database } from '@mosmox-ui/icons';
<InputNumber label="Quantity" min={0} max={20} defaultValue={1} />The control uses a native text input with role="spinbutton", so users can type a value directly or use the step controls.
The field accepts negative values and decimal values. A comma typed as the decimal separator is normalized to a period. While editing, incomplete states such as an empty string, -, or a trailing decimal point are preserved; an empty or incomplete value is reported to onChange as undefined.
Integer and decimal values
Use step to define the increment. Decimal steps are supported, and precision controls how many decimal places are shown after a step or blur normalization.
<InputNumber label="Seats" defaultValue={4} step={1} precision={0} />
<InputNumber label="Price" defaultValue={19.95} min={0} step={0.05} precision={2} />precision={0} is useful for integer presentation. The input still accepts an incomplete value while the user is editing; formatting is applied when the value is stepped or normalized.
Minimum, maximum, and blur behavior
min and max constrain values produced by the increment/decrement controls. By default, a value outside those bounds is clamped when the input loses focus.
<InputNumber
label="Completion"
defaultValue={50}
min={0}
max={100}
step={5}
clampOnBlur
/>Set clampOnBlur={false} when validation should be handled by the parent instead. The component still applies bounds to button and keyboard stepping.
Controls and keyboard interaction
controls controls the visibility of the increment and decrement buttons and defaults to true.
ArrowUpincrements bystep.ArrowDowndecrements bystep.- The buttons and keyboard interaction respect
min,max,disabled, andreadOnly. onStepreceives the resulting value and the direction (incrementordecrement).
<InputNumber
label="Zoom"
defaultValue={100}
min={25}
max={400}
step={25}
controls
onStep={(value, direction) => console.log(direction, value)}
/>Use controls={false} for a compact text-only numeric field while retaining keyboard stepping.
The buttons are displayed horizontally in the order decrement, then increment. Their accessible names are Decrease value and Increase value, and they use down-arrow and up-arrow icons. When a bound is reached, the corresponding button is disabled.
Prefix, suffix, and icons
Use prefix and suffix for units or formatting context. leadingIcon and trailingIcon accept any React node and are decorative; the accessible label remains the source of the control name.
<InputNumber label="Price" prefix="$" defaultValue={29.99} precision={2} />
<InputNumber label="Duration" defaultValue={30} suffix="min" />
<InputNumber label="Storage" leadingIcon={<Database size={16} />} suffix="GB" />Sizes and visual variants
Three sizes are available: default, sm, and xs. The size applies to the field and its step controls.
<InputNumber label="Default" size="default" defaultValue={10} />
<InputNumber label="Small" size="sm" defaultValue={10} />
<InputNumber label="Extra small" size="xs" defaultValue={10} />Use variant="filled" for the filled control surface. The default is variant="outline".
The default field width is 240px, with a 100px minimum and a maximum of 100% of its parent. Use width, minWidth, and maxWidth to provide CSS width values such as numbers in pixels, percentages, or responsive expressions.
fullWidth makes the field fill its parent width. It has the highest priority: when enabled, the component uses width: 100%, min-width: 0, and max-width: none, ignoring width, minWidth, and maxWidth values. This is useful inside narrow grid columns or responsive forms.
<InputNumber label="Fixed width" width={280} />
<InputNumber label="Constrained width" minWidth={160} maxWidth={360} />
<InputNumber label="Fill available space" width={120} minWidth={100} maxWidth={200} fullWidth />Controlled and uncontrolled values
Use defaultValue for an uncontrolled field. Use value and onChange when the parent owns the value. The change callback receives number | undefined, where undefined represents an empty or incomplete value.
const [amount, setAmount] = useState<number | undefined>(10);
<InputNumber
label="Amount"
value={amount}
onChange={(nextValue) => setAmount(nextValue)}
/>Description, validation, and states
description adds supporting text and error adds an accessible validation message and error styling. disabled prevents all interaction. readOnly keeps the value focusable and visible while preventing edits and stepping.
loading disables the input and step buttons and displays only a compact spinner before the buttons. No loading text is rendered inside the control.
<InputNumber
label="Budget"
description="Enter a value between 0 and 10,000."
min={0}
max={10000}
error="Budget exceeds the approved limit."
/>
<InputNumber label="Locked value" value={240} readOnly />
<InputNumber label="Unavailable" value={0} disabled />
<InputNumber label="Saving" value={240} loading />Accessibility
The visible label is associated with the input. The input exposes spinbutton semantics and communicates min, max, and the current numeric value through ARIA attributes. Description and error text are connected through aria-describedby, and errors set aria-invalid unless an explicit value is provided. The step buttons have explicit accessible names and remain keyboard reachable.
API Reference
| Property | Type | Default | Description |
|---|---|---|---|
label | string | — | Visible field label associated with the input. |
description | string | — | Supporting text below the label. |
error | string | — | Validation message and error styling. |
value | number | null | — | Controlled numeric value. null clears the field. |
defaultValue | number | — | Initial uncontrolled value. |
onChange | (value: number | undefined, event: ChangeEvent<HTMLInputElement>) => void | — | Called when the typed value changes. Empty or incomplete values are reported as undefined. |
min | number | — | Lower bound for stepping and optional blur clamping. |
max | number | — | Upper bound for stepping and optional blur clamping. |
step | number | 1 | Increment used by buttons and arrow keys. |
precision | number | — | Number of decimal places used when formatting values. |
clampOnBlur | boolean | true | Clamp typed values to min/max on blur. |
controls | boolean | true | Show increment and decrement buttons. |
onStep | (value: number, direction: 'increment' | 'decrement') => void | — | Called after a button or arrow-key step; use it to update a controlled value. |
size | 'default' | 'sm' | 'xs' | 'default' | Field and control size. |
variant | 'outline' | 'filled' | 'outline' | Surface treatment. |
fullWidth | boolean | false | Expand the field to its parent width. |
width | CSSProperties['width'] | 240px | Preferred component width. |
minWidth | CSSProperties['minWidth'] | 100px | Minimum component width. |
maxWidth | CSSProperties['maxWidth'] | 100% | Maximum component width. |
prefix | ReactNode | — | Content before the numeric value. |
suffix | ReactNode | — | Content after the numeric value. |
leadingIcon | ReactNode | — | Decorative icon before the value. |
trailingIcon | ReactNode | — | Decorative icon after the value. |
disabled | boolean | false | Disable the field and controls. |
readOnly | boolean | false | Keep the field focusable without allowing changes. |
loading | boolean | false | Disable interaction and show a loading indicator. |
Native input props
InputNumber forwards supported InputHTMLAttributes<HTMLInputElement> props to the underlying input. This includes id, name, placeholder, required, autoComplete, autoFocus, form, tabIndex, onBlur, onKeyDown, and aria-* attributes. The component combines aria-describedby with its generated description and error IDs.
The following native props are owned by the component and should not be supplied independently: type, value, defaultValue, size, and onChange. Use the component versions documented above.
Controlled value details
value={null} clears a controlled field. value={undefined} means that the component is uncontrolled, so use defaultValue for its initial value. For a controlled field, update the parent value from onChange for typing and from onStep for buttons or arrow keys.
Theme tokens
Shared visual values come from the theme tokens and can be changed globally with ConfigProvider:
| Token | Purpose |
|---|---|
--mm-color-text | Numeric value and label text. |
--mm-color-surface | Outline field surface. |
--mm-color-control-filled | Filled variant and button hover surface. |
--mm-color-control-border | Field and control divider borders. |
--mm-color-control-icon | Adornment and step icon color. |
--mm-color-muted-text | Prefix, suffix, and description text. |
--mm-color-placeholder | Disabled step controls. |
--mm-color-primary | Focus border and focus ring. |
--mm-color-focus | Keyboard focus ring. |
--mm-color-danger-text | Validation error styling. |
--mm-radius-control | Field corner radius. |
--mm-font-family-sans | Control typography. |
--mm-font-weight-normal | Numeric input font weight. |
--mm-font-weight-medium | Label font weight. |
--mm-size-icon | Leading and trailing icon box size. |
--mm-space-control-icon-gap | Spacing between input content, adornments, and icons. |