Raqam
API Reference

NumberField Components

Headless compound components — the fastest way to build a number field.

The NumberField namespace exports headless compound components that handle all wiring internally via React Context. No prop drilling, no ref management.

Import

import { NumberField } from "raqam";

Component tree

<NumberField.Root ...>          {/* context provider + state */}
  <NumberField.Label />         {/* <label> */}
  <NumberField.Group>           {/* <div role="group"> */}
    <NumberField.Decrement />   {/* <button> */}
    <NumberField.Input />       {/* <input type="text"> */}
    <NumberField.Increment />   {/* <button> */}
  </NumberField.Group>
  <NumberField.ScrubArea>       {/* drag-to-change wrapper */}
    <NumberField.ScrubAreaCursor /> {/* custom pointer cursor */}
  </NumberField.ScrubArea>
  <NumberField.Formatted />     {/* read-only formatted display */}
  <NumberField.Description />   {/* helper text */}
  <NumberField.ErrorMessage />  {/* validation error */}
  <NumberField.HiddenInput />   {/* form submission value */}
</NumberField.Root>

Only Root and Input are required. All others are optional.


NumberField.Root

Context provider. Accepts every useNumberFieldState and useNumberField option (step, formatOptions, validate, copyBehavior, stepHoldDelay, label, aria-*, …) — the most common ones plus the Root-only props are:

PropTypeDescription
localestringBCP 47 locale. Defaults to the current runtime locale.
formatOptionsIntl.NumberFormatOptionsNumber format configuration.
value / defaultValuenumber | nullControlled/uncontrolled value.
onChange(v: number | null) => voidCalled whenever the parsed numeric value changes.
onValueChange(v, details) => voidLike onChange, plus { reason, formattedValue }.
onValueCommitted(v, details) => voidFires only on commit, with { reason: "blur" | "keyboard" }.
minValue / maxValuenumberConstraints.
step / largeStep / smallStepnumberStep sizes.
validate(v: number | null) => boolean | string | nullCustom validator.
disabled / readOnlybooleanInteraction state.
requiredbooleanMarks the field as required.
allowNegative / allowDecimalbooleanInput constraints.
prefix / suffixstringAffixes.
namestringGenerates hidden input props for native form submission.
allowMouseWheelbooleanEnable wheel-based increment/decrement.
copyBehavior"formatted" | "raw" | "number"Clipboard behaviour.
allowOutOfRangebooleanSkip clamping; show aria-invalid instead.
className / stylestring / CSSPropertiesApplied to the root wrapper <div>.

Data attributes set on the root element:

AttributeSet when
data-focusedInput is focused
data-invalidValue is invalid
data-disableddisabled is true
data-readonlyreadOnly is true
data-requiredrequired is true
data-scrubbingA scrub interaction is active

onValueChange fires on every change and reports how it happened via reason ("input", "clear", "paste", "keyboard", "increment", "decrement", "wheel", "scrub", or "blur"). onValueCommitted fires only when the value settles — on blur (reason: "blur") or when the user presses Enter (reason: "keyboard") — after formatting and clamping, and receives the final value. Use it to persist a value or fire a request without reacting to every keystroke.

<NumberField.Root
  locale="en-US"
  minValue={0}
  onValueCommitted={(value, { reason }) => save(value)} // reason: "blur" | "keyboard"
>
  <NumberField.Input />
</NumberField.Root>

See Formatting & Behavior → Change reasons for the full breakdown.


NumberField.Input

The text input. Accepts all standard <input> props except type (always "text").

PropTypeDescription
renderRenderPropSwap the element (e.g. a styled component).

NumberField.Label

<NumberField.Label>Price</NumberField.Label>

Renders a <label> with htmlFor wired to the input id. Accepts all <label> props.


NumberField.Group

Wraps the input and stepper buttons. Renders <div role="group">. Accepts all <div> props.


NumberField.Increment / NumberField.Decrement

Stepper buttons. Accepts all <button> props.

<NumberField.Decrement>−</NumberField.Decrement>
<NumberField.Increment>+</NumberField.Increment>

Buttons are automatically disabled when at minValue/maxValue or when the field is disabled or readOnly.

Press-and-hold acceleration: hold for stepHoldDelay ms → interval fires at stepHoldInterval ms.

Each button carries an accessible aria-label (defaults "Increase" / "Decrease"). Localize them via the incrementLabel / decrementLabel props on <NumberField.Root>, or override per button with aria-label on <NumberField.Increment> / <NumberField.Decrement>.

<NumberField.Root incrementLabel="Augmenter" decrementLabel="Diminuer">
  <NumberField.Decrement>−</NumberField.Decrement>
  <NumberField.Increment>+</NumberField.Increment>
</NumberField.Root>

NumberField.ScrubArea

Wraps any element; dragging over it adjusts the value via Pointer Lock API.

PropTypeDefaultDescription
direction"horizontal" | "vertical" | "both""horizontal"Drag axis.
pixelSensitivitynumber4Pixels dragged per step.
labelstring"Scrub to change value"Accessible label (aria-label) for the scrub area.
renderRenderPropCustom element.
<NumberField.ScrubArea direction="horizontal" pixelSensitivity={2}>
  Drag to adjust
</NumberField.ScrubArea>

The element renders as role="slider" with tabIndex={0}, so it is keyboard accessible: arrow keys (←/→ or ↑/↓) step the value while it is focused. It also exposes the slider value ARIA — aria-valuenow, aria-valuemin, aria-valuemax, aria-valuetext (the formatted value), and aria-disabled — so assistive tech announces the current value and range. The CSS cursor reflects the axis (ew-resize / ns-resize / move), and data-scrubbing="" is set while a pointer-lock drag is active.

pixelSensitivity values below 1 are clamped to 1.


NumberField.ScrubAreaCursor

Optional. Renders only while scrubbing so you can show custom visual feedback next to the active scrub handle. Accepts <span> props.

<NumberField.ScrubArea>
  <span>Drag me</span>
  <NumberField.ScrubAreaCursor>

  </NumberField.ScrubAreaCursor>
</NumberField.ScrubArea>

NumberField.Formatted

A <span> (or custom element) that displays the current formatted value as read-only text. Useful for dual-display UIs (edit + display side by side). It renders with aria-hidden="true" so screen readers announce the value once (from the input), not twice.

<NumberField.Formatted />
{/* Renders: "$1,234.56" */}

This mirrors state.inputValue and updates live as the user edits. For formatting outside a NumberField.Root, use useNumberFieldFormat instead.


NumberField.Description

Renders helper text as a <p>. It is automatically associated with the input for screen readers: while a <NumberField.Description> is mounted, the input's aria-describedby points at it. The reference is wired up after mount (the same way aria-labelledby is) and removed when the description unmounts, so there is never a dangling reference. See Accessibility.

<NumberField.Root locale="en-US">
  <NumberField.Input />
  <NumberField.Description>
    Enter a value between 0 and 100.
  </NumberField.Description>
</NumberField.Root>

If you also pass aria-describedby on <NumberField.Input>, your value is merged with the description id (your value first), not dropped — e.g. aria-describedby="my-hint amount-description".


NumberField.ErrorMessage

Displays validation errors. Has role="alert" and is only shown when the field is invalid.

If you pass no children, it renders state.validationError automatically:

<NumberField.ErrorMessage />
{/* Renders: "Must be a positive number" */}

Or supply your own content:

<NumberField.ErrorMessage>
  Custom error text.
</NumberField.ErrorMessage>

NumberField.HiddenInput

An <input type="hidden"> for HTML form submission. Its value is always the raw number (no formatting). To enable it, pass name to NumberField.Root.

<NumberField.Root locale="en-US" name="price">
  <NumberField.Input />
  <NumberField.HiddenInput />
</NumberField.Root>

render prop

The visual building blocks (Label, Group, Input, Increment, Decrement, ScrubArea, ScrubAreaCursor, and Formatted) accept a render prop for element replacement (no asChild peer deps required):

<NumberField.Input
  render={(props) => <MyStyledInput {...props} />}
/>

Use data-focused, data-invalid, data-disabled on NumberField.Root for pure-CSS state styling — no JavaScript class toggling needed.

On this page