Mosmox UI
Current version
GitHubnpmjs.com
Forms & Inputs

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.

  • ArrowUp increments by step.
  • ArrowDown decrements by step.
  • The buttons and keyboard interaction respect min, max, disabled, and readOnly.
  • onStep receives the resulting value and the direction (increment or decrement).
<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

PropertyTypeDefaultDescription
labelstring—Visible field label associated with the input.
descriptionstring—Supporting text below the label.
errorstring—Validation message and error styling.
valuenumber | null—Controlled numeric value. null clears the field.
defaultValuenumber—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.
minnumber—Lower bound for stepping and optional blur clamping.
maxnumber—Upper bound for stepping and optional blur clamping.
stepnumber1Increment used by buttons and arrow keys.
precisionnumber—Number of decimal places used when formatting values.
clampOnBlurbooleantrueClamp typed values to min/max on blur.
controlsbooleantrueShow 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.
fullWidthbooleanfalseExpand the field to its parent width.
widthCSSProperties['width']240pxPreferred component width.
minWidthCSSProperties['minWidth']100pxMinimum component width.
maxWidthCSSProperties['maxWidth']100%Maximum component width.
prefixReactNode—Content before the numeric value.
suffixReactNode—Content after the numeric value.
leadingIconReactNode—Decorative icon before the value.
trailingIconReactNode—Decorative icon after the value.
disabledbooleanfalseDisable the field and controls.
readOnlybooleanfalseKeep the field focusable without allowing changes.
loadingbooleanfalseDisable 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:

TokenPurpose
--mm-color-textNumeric value and label text.
--mm-color-surfaceOutline field surface.
--mm-color-control-filledFilled variant and button hover surface.
--mm-color-control-borderField and control divider borders.
--mm-color-control-iconAdornment and step icon color.
--mm-color-muted-textPrefix, suffix, and description text.
--mm-color-placeholderDisabled step controls.
--mm-color-primaryFocus border and focus ring.
--mm-color-focusKeyboard focus ring.
--mm-color-danger-textValidation error styling.
--mm-radius-controlField corner radius.
--mm-font-family-sansControl typography.
--mm-font-weight-normalNumeric input font weight.
--mm-font-weight-mediumLabel font weight.
--mm-size-iconLeading and trailing icon box size.
--mm-space-control-icon-gapSpacing between input content, adornments, and icons.