Cloud|

@k2b/ui

NumberInput

Bounded and formatted numeric input with steppers and clear state.

NumberInput@k2b/ui

Accessor-controlled numeric input with raw focused text, committed bounds, precision, steppers, units, and an explicit empty state.

€gross
%

TSX

Copy
<NumberInput
label="Budget"
value={budget}
onValueChange={setBudget}
prefix="€"
suffix="gross"
decimalPlaces={2}
min={0}
step={0.5}
clearable
/>
<NumberInput label="Capacity" value={capacity} onValueChange={setCapacity} suffix="%" min={0} max={100} step={5} />
<NumberInput label="Workers" value={count} onValueChange={setCount} min={1} max={64} step={1} />

NumberInput is an accessor-controlled numeric field with an explicit empty state. The parent owns the number and decides when to persist it.

Use NumberInput

Use it for counts, limits, percentages, currency amounts, and other numeric form values.

Use TextInput when the value is an identifier that only looks numeric, such as an account number or postal code.

Import

tsx
import { NumberInput } from "@k2b/ui";

Value and events

Pass value directly or as an accessor returning number | null. null and an omitted value render as empty; callbacks emit null when the field is cleared.

onValueChange receives the parsed value while the user types. onValueCommit receives the normalized value after blur, a stepper click, or the default clear action.

The component keeps the raw text while focused, so intermediate input is not lost. A trailing decimal separator survives only when the effective decimalPlaces is greater than 0; with an integer step a typed 12. is normalized to 12.

Numeric rules

  • decimalPlaces defaults to the fraction digits of step (step={0.01} accepts two decimals, the default step={1} accepts none). Set it explicitly when the accepted precision differs from the step grid; an explicit value always wins.
  • The visible decimal separator follows the effective locale (locale prop, then LocaleProvider, then <html lang>, then "en"); both comma and dot are accepted while typing. The controlled value stays a canonical JavaScript number and aria-valuenow stays numeric.
  • Editable text never shows grouping separators.
  • allowNegative defaults to true.
  • min and max clamp committed values.
  • step defaults to 1 and snaps committed values to its grid.
  • The default stepper buttons are integrated into the left and right edges of the input.
  • showSteppers={false} hides the buttons without disabling typed input.
  • disableSteppers disables only the buttons.
  • The placeholder and numeric value are both right-aligned; the placeholder is visually quieter than entered values.

prefix and suffix display short units inside the field. clearable adds an explicit empty-state action.

Edit a number in a property row

Set appearance="plain" when the number is a property shown in a DescriptionList row, such as an estimate. The value reads as text in the row's type, followed by its suffix, and the field is exactly as wide as that text. There are no steppers or clear button: a click anywhere in the row edits the value, Enter or leaving the field commits, Escape restores the current value, and emptying the text clears it. Enter and Escape keep focus in the field. The suffix hides while the field is empty, so the placeholder reads alone. Commit through onValueCommit; leaving the field fires it also when the value did not change, but not again right after Enter.

Pass formatValue to show a set value in a friendlier form while the field is not being edited, such as 90 minutes as 1 h 30 min. Editing shows the plain number with its suffix; at rest the formatted text stands alone.

On touch devices the field edits at 16 px like every other input, so iOS does not zoom. At rest the value keeps the row's size.

tsx
<NumberInput
  aria-label="Estimate"
  appearance="plain"
  placeholder="No estimate"
  suffix="min"
  min={1}
  value={estimate}
  formatValue={formatDuration}
  onValueCommit={saveEstimate}
/>;

API reference

See shared field props for FieldProps, ValueFieldProps<T> and MaybeAccessor<T>.

ts
type NumberInputProps = Omit<
  JSX.InputHTMLAttributes<HTMLInputElement>,
  "max" | "min" | "onChange" | "onInput" | "prefix" | "step" | "type" | "value" | keyof ValueFieldProps<number | null>
> &
  ValueFieldProps<number | null> & {
    max?: number;
    min?: number;
    step?: number;
    decimalPlaces?: number;
    allowNegative?: boolean;
    clearable?: boolean;
    onClear?: () => void;
    clearLabel?: string;
    increaseLabel?: string;
    decreaseLabel?: string;
    locale?: string;
    showSteppers?: boolean;
    disableSteppers?: boolean;
    icon?: string;
    activeIcon?: string;
    prefix?: JSX.Element;
    suffix?: JSX.Element;
    appearance?: ChoiceAppearance;
    formatValue?: (value: number) => string;
  };

appearance defaults to "field"; with "plain", showSteppers defaults to false. formatValue only changes the resting text of a plain field. onClear overrides the built-in reset to null; the host must then report its own value/commit update. icon and activeIcon replace the idle and focused icons.

Accessibility

Prefer a visible label. Without one, the placeholder becomes the accessible name, with Enter number as the final fallback.

The input exposes spinbutton semantics and finite minimum, maximum, and current values. Its value text includes a string suffix, such as 45 min, or the formatValue text; an explicit aria-valuetext wins. The stepper and clear controls have accessible names; override them per instance with increaseLabel, decreaseLabel, and clearLabel when the surrounding product is not English. Descriptions and reactive errors are connected to the field.

Runtime

The field renders in server HTML. Input filtering, raw-text preservation, steppers, clearing, and callbacks require browser code.

Example

tsx
const [price, setPrice] = createSignal<number | null>(12.5);

<NumberInput
  label="Price"
  value={price}
  onValueChange={setPrice}
  min={0}
  step={0.01}
  decimalPlaces={2}
  suffix="€"
  clearable
  showSteppers={false}
/>;

Connect a coding agent

The Cloud skill provides compact working instructions. MCP supplies exact current documentation when details matter.

CLI command
bunx skills add https://docs.example.com

This command installs the working instructions published by this website in the selected coding agent.

For exact, current details, also connect the MCP server through one of the agent tabs.

Installation uses the open-source Vercel Skills CLI.

The skill and MCP complement each other: the skill describes workflows, while MCP supplies current documentation.