Raqam

Getting Started

Install raqam and build your first number field in under five minutes.

Installation

npm install raqam

Peer dependencies: React 18 or 19.

Your first field

Integer field with min 0 and steppers.

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>
KeyAction
↑ / ↓step (default: 1)
Shift + ↑/↓largeStep (default: 10)
Ctrl/Cmd + ↑/↓smallStep (default: 0.1)
Page Up/DownlargeStep
Home / Endjump 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.

On this page