Raqam
API Reference

useNumberFieldState

State management hook for number fields — use when building the Hook API pattern.

useNumberFieldState manages all state for a number field: the formatted display string, the underlying numeric value, validation, focus, and scrubbing.

Pair it with useNumberField to get ARIA prop objects for your elements.

Import

import { useNumberFieldState } from "raqam";
// or
import { useNumberFieldState } from "raqam/react";

Signature

function useNumberFieldState(
  options: UseNumberFieldStateOptions
): NumberFieldState;

Options

Core

PropTypeDefaultDescription
localestringruntime defaultBCP 47 locale tag. Drives Intl.NumberFormat.
formatOptionsIntl.NumberFormatOptions{}Passed to Intl.NumberFormat.
valuenumber | nullControlled value.
defaultValuenumber | nullnullUncontrolled initial value.
onChange(value: number | null) => voidCalled whenever the parsed numeric value changes.

Constraints

PropTypeDefaultDescription
minValuenumberMinimum allowed value.
maxValuenumberMaximum allowed value.
stepnumber1Normal step (↑/↓ arrow keys).
largeStepnumberstep × 10Large step (Shift+↑/↓, Page Up/Down).
smallStepnumberstep × 0.1Small step (Ctrl/Cmd+↑/↓).
clampBehavior"blur" | "strict" | "none""blur"When to clamp to min/max — clamp on blur, reject out-of-range keystrokes, or never.
allowOutOfRangebooleanfalseKeep out-of-range values as typed and committed; sets aria-invalid instead of clamping.
allowNegativebooleantrueAllow negative values.
allowDecimalbooleantrueAllow decimal input.

See Formatting & Behavior → Clamping for how clampBehavior and allowOutOfRange interact.

Formatting

PropTypeDefaultDescription
maximumFractionDigitsnumberOverride formatOptions.maximumFractionDigits.
minimumFractionDigitsnumberOverride formatOptions.minimumFractionDigits.
fixedDecimalScalebooleanfalseAlways show maximumFractionDigits decimal places. Requires maximumFractionDigits (explicit or from the format) to take effect.
prefixstringPrepend a string (e.g. "$").
suffixstringAppend a string (e.g. " kg").
liveFormatbooleantrueFormat on every keystroke (disable for IME locales).

Validation

PropTypeDefaultDescription
validate(v: number | null) => boolean | string | null | undefinedCustom validator. Return true/null/undefined for valid, false for invalid, or a string to set an error message.
requiredbooleanfalseMark the field as required (sets aria-required / data-required).

Escape hatches

PropTypeDefaultDescription
formatValue(v: number) => stringReplace the default formatter.
parseValue(s: string) => { value: number | null; isIntermediate: boolean }Replace the default parser.
onRawChange(raw: string | null) => voidCalled with the unformatted, precision-preserving numeric string (grouping / currency / prefix / suffix stripped, locale decimal normalized to ., typed trailing zeros kept). For rescaling / non-invertible styles (percent, compact, scientific, unit, custom formatValue) it falls back to the canonical numeric string of the value. For arbitrary precision.

Interaction

PropTypeDefaultDescription
disabledbooleanfalseDisable all interaction.
readOnlybooleanfalseAllow focus, disallow editing.

Press-and-hold timing (stepHoldDelay, stepHoldInterval) and other behavior-only props (copyBehavior, allowMouseWheel, name, label, aria-*) live on the behavior hook — see useNumberField.

Return value — NumberFieldState

PropertyTypeDescription
inputValuestringThe formatted string currently shown in the input.
numberValuenumber | nullThe parsed numeric value (null when empty/invalid).
rawValuestring | nullThe unformatted, precision-preserving numeric string (grouping / currency / prefix / suffix stripped, locale decimal normalized to ., typed trailing zeros kept). For rescaling / non-invertible styles (percent, compact, scientific, unit, custom formatValue) it falls back to the canonical numeric string of the value.
isFocusedbooleanWhether the input is focused.
isScrubbingbooleanWhether a scrub drag is in progress.
canIncrementbooleanFalse when at maxValue or disabled.
canDecrementbooleanFalse when at minValue or disabled.
validationState"valid" | "invalid"Result of the validate callback plus range checks.
validationErrorstring | nullError message from validate, or null.
increment(amount?)(amount?: number) => voidStep up by step (or by amount when given).
decrement(amount?)(amount?: number) => voidStep down by step (or by amount when given).
incrementToMax()() => voidJump to maxValue.
decrementToMin()() => voidJump to minValue.
setInputValue(s, knownValue?)(s: string, knownValue?: number | null) => voidSet the display string. Pass knownValue when s is a formatted string that can't be reparsed (compact, scientific, unit).
setNumberValue(n)(n: number | null) => voidDirectly set the numeric value.
commit()() => number | nullTrigger blur-time formatting and clamping; returns the committed value.
setIsFocused(b)(b: boolean) => voidSync focus state (called by useNumberField).
setIsScrubbing(b)(b: boolean) => voidSync scrub state (called by useScrubArea).
optionsUseNumberFieldStateOptionsThe resolved options object (read by useNumberField/useScrubArea).

Example

import { useRef } from "react";
import { useNumberFieldState, useNumberField } from "raqam";

function MyInput() {
  const ref = useRef<HTMLInputElement>(null);

  const state = useNumberFieldState({
    locale: "en-US",
    formatOptions: { style: "currency", currency: "USD" },
    minValue: 0,
    onChange: (value) => console.log("parsed value:", value),
  });

  const { labelProps, inputProps } = useNumberField(
    { locale: "en-US", formatOptions: { style: "currency", currency: "USD" } },
    state,
    ref,
  );

  return (
    <>
      <label {...labelProps}>Amount</label>
      <input {...inputProps} ref={ref} />
      <p>Current: {state.numberValue}</p>
    </>
  );
}

useNumberFieldState does NOT read from or write to the DOM. It's a pure React state machine. Pair it with useNumberField for DOM integration.

If you need change metadata such as "increment" vs "paste" vs "blur", use NumberField.Root with onValueChange instead of the raw state hook.

On this page