Getting Started
Install raqam and build your first number field in under five minutes.
Installation
npm install raqamPeer dependencies: React 18 or 19.
Your first field
Integer field with min 0 and steppers.
Component API (recommended)
import { NumberField } from "raqam";
export function QuantityInput() {
return (
<NumberField.Root locale="en-US" defaultValue={1} minValue={0}>
<NumberField.Label>Quantity</NumberField.Label>
<NumberField.Group>
<NumberField.Decrement>−</NumberField.Decrement>
<NumberField.Input />
<NumberField.Increment>+</NumberField.Increment>
</NumberField.Group>
</NumberField.Root>
);
}Hook API (maximum control)
import { useRef } from "react";
import { useNumberFieldState, useNumberField } from "raqam";
export function QuantityInput() {
const inputRef = useRef<HTMLInputElement>(null);
// Share one options object — useNumberField builds its own formatter/parser
// and needs the same formatting-relevant options as the state hook.
const options = { locale: "en-US", defaultValue: 1, minValue: 0 };
const state = useNumberFieldState(options);
const { labelProps, groupProps, inputProps, incrementButtonProps, decrementButtonProps } =
useNumberField(options, state, inputRef);
return (
<div>
<label {...labelProps}>Quantity</label>
<div {...groupProps}>
<button {...decrementButtonProps}>−</button>
<input {...inputProps} ref={inputRef} />
<button {...incrementButtonProps}>+</button>
</div>
</div>
);
}The NumberField components do this wiring for you, so reach for the Hook API
only when you need full control of the DOM. See
useNumberField for why both hooks need the same options.
Controlled vs uncontrolled
Like all React form controls, raqam supports both patterns.
Uncontrolled (defaultValue)
<NumberField.Root locale="en-US" defaultValue={42}>
<NumberField.Input />
</NumberField.Root>Controlled (value + onChange)
const [value, setValue] = useState<number | null>(42);
<NumberField.Root locale="en-US" value={value} onChange={setValue}>
<NumberField.Input />
</NumberField.Root>onChange receives number | null whenever the parsed numeric value changes.
If you need metadata about how the change happened, use onValueChange on
NumberField.Root for { reason, formattedValue }. To react only when the value
settles (on blur or Enter), use onValueCommitted instead — see
Formatting & Behavior → Change reasons.
Currency formatting
<NumberField.Root
locale="en-US"
formatOptions={{ style: "currency", currency: "USD" }}
defaultValue={0}
minValue={0}
>
<NumberField.Label>Price</NumberField.Label>
<NumberField.Group>
<NumberField.Decrement>−</NumberField.Decrement>
<NumberField.Input />
<NumberField.Increment>+</NumberField.Increment>
</NumberField.Group>
</NumberField.Root>The formatOptions prop accepts any Intl.NumberFormatOptions. Use
presets for common configurations.
Adding min/max/step
<NumberField.Root
locale="en-US"
defaultValue={50}
minValue={0}
maxValue={100}
step={5}
largeStep={25}
>
<NumberField.Input />
</NumberField.Root>| Key | Action |
|---|---|
| ↑ / ↓ | step (default: 1) |
| Shift + ↑/↓ | largeStep (default: 10) |
| Ctrl/Cmd + ↑/↓ | smallStep (default: 0.1) |
| Page Up/Down | largeStep |
| Home / End | jump to minValue / maxValue |
Form integration
Use NumberField.HiddenInput to submit the raw numeric value in an HTML form.
<form method="post" action="/api/price">
<NumberField.Root locale="en-US" name="price" defaultValue={9.99}>
<NumberField.Label>Price</NumberField.Label>
<NumberField.Input />
<NumberField.HiddenInput />
</NumberField.Root>
<button type="submit">Save</button>
</form>For library-managed forms see react-hook-form and Formik recipes.
TypeScript
raqam is written in TypeScript. Key types:
import type {
UseNumberFieldStateOptions,
NumberFieldState,
UseNumberFieldProps,
NumberFieldAria,
ChangeReason,
} from "raqam";Use raqam/core for server-side formatting (RSC, edge functions). It has zero
React dependency. See Next.js guide.