Files
skills/docs/ui/react/react-ui/src/components/input/input-number/input-number.md
2026-05-29 08:20:56 +01:00

3.8 KiB

<InputNumber>

(no submodules)

Purpose

Locale-aware numeric input. Parses incoming text using the active locale's decimal and grouping separators, accepts locale-specific digit glyphs (٣٫١٤ in Arabic-Indic), and emits a canonical number | null through onValueChange. Switches to a raw editable buffer on focus to avoid cursor jumps caused by re-formatting mid-keystroke; renders the locale-formatted value on blur. The plain <Input> is unaffected — <InputNumber> is a sibling primitive consumers opt into per call site, so existing form code is not disturbed.

Features

  • Per-locale separator detection via Intl.NumberFormat.formatToParts(12345.6) — handles en-GB 1,234.5, de-DE 1.234,5, fr-FR 1 234,5, ar-AE Arabic-Indic glyphs.
  • Digit-folding for non-Latin numbering systems — Eastern Arabic, Devanagari, Bengali, etc.
  • Focus / blur buffer split — raw Latin during edit, locale formatted at rest.
  • Empty input emits null (not 0 or NaN).
  • Invalid input on blur reverts to the previously valid formatted value rather than corrupting state.
  • Controlled OR uncontrolled — pass value (controlled) or defaultValue (uncontrolled).
  • displayOptions: Intl.NumberFormatOptions lets the rest-state format render percent, currency, scientific, etc.

Usage: Basic Example

import { InputNumber } from "@suaveplan/react-ui";

const [amount, setAmount] = useState<number | null>(null);

<InputNumber value={amount} onValueChange={setAmount} />

Usage: Advanced Example

Currency-style display (rest-state shows 1,234.50 while focused buffer shows 1234.5):

<InputNumber
    value={price}
    onValueChange={setPrice}
    displayOptions={{
        minimumFractionDigits: 2,
        maximumFractionDigits: 2,
    }}
    inputMode="decimal"
    aria-label="Unit price"
/>

Bind directly to a form library's controlled value, with locale override for a multi-currency entry form:

<InputNumber
    locale="de-DE"
    value={field.value}
    onValueChange={field.onChange}
    onBlur={field.onBlur}
    name={field.name}
    ref={field.ref}
/>

API Reference

<InputNumber value? defaultValue? onValueChange? locale? displayOptions? {...rest}>

  • value?: number | null — controlled. null renders empty.
  • defaultValue?: number | null — uncontrolled initial.
  • onValueChange?: (value: number | null) => void — fires on blur.
  • locale?: string — defaults to useReactUiLocale().locale.
  • displayOptions?: Intl.NumberFormatOptions.
  • All other <input> HTML attributes are forwarded EXCEPT value, defaultValue, onChange, type (the component owns those).

parseLocaleNumber(text, locale): number | null

Standalone utility for parsing a locale-formatted numeric string. Used internally by the component; exported for consumers needing the same parse logic outside the component.

Implementation Notes

  • The Intl.NumberFormat instance is cached per (locale, displayOptions) via useMemo. The separator probe (formatToParts(12345.6)) is also cached per locale at module level.
  • onValueChange fires only on blur to avoid emitting null for every transient unparseable keystroke. Forms that need keystroke-level validation can subscribe to onChange on the underlying <input> directly via the spread (we do not own onChange once the user has supplied one).
  • The component uses type="text" with inputMode="decimal" rather than type="number". type="number" rejects locale-specific separators in Chrome and triggers spinner UI consumers usually do not want.
  • When a parent re-renders with an externally-changed value while the input is NOT focused, the displayed value re-formats. While focused, the focused-buffer text wins so the user is not interrupted mid-edit.