Skip to content

Next.js App Router

raqam is a client-side library (it uses React hooks, DOM APIs, and browser Intl). All components and hooks must run in a Client Component.

components/price-input.tsx
"use client";
import { NumberField } from "raqam";
export function PriceInput({ value, onChange }: {
value: number | null;
onChange: (v: number | null) => void;
}) {
return (
<NumberField.Root
locale="en-US"
formatOptions={{ style: "currency", currency: "USD" }}
value={value}
onChange={onChange}
>
<NumberField.Label>Price</NumberField.Label>
<NumberField.Group>
<NumberField.Decrement></NumberField.Decrement>
<NumberField.Input />
<NumberField.Increment>+</NumberField.Increment>
</NumberField.Group>
</NumberField.Root>
);
}

For SSR formatting (price display, report generation, email templates), import from raqam/server — it has zero React dependency and runs in any server context including Edge Runtime:

// app/products/page.tsx (Server Component)
import { createFormatter } from "raqam/server";
import { presets } from "raqam/server";
const fmt = createFormatter({
locale: "en-US",
formatOptions: presets.currency("USD"),
});
export default function ProductsPage() {
const price = fmt.format(1234.56); // "$1,234.56"
return <div>Price: {price}</div>;
}

raqam/server is an alias for raqam/core. It exports createFormatter, createParser, normalizeDigits, registerLocale, and presets.

A common pattern: the Server Component renders the page shell and passes formatted prices as props, while a nested Client Component handles the editable field.

// app/checkout/page.tsx (Server Component)
import { createFormatter } from "raqam/server";
import { CheckoutForm } from "./checkout-form";
export default async function CheckoutPage() {
const product = await getProduct();
const formatter = createFormatter({
locale: "en-US",
formatOptions: { style: "currency", currency: "USD" },
});
return (
<CheckoutForm
productName={product.name}
defaultPrice={product.price}
formattedPrice={formatter.format(product.price)}
/>
);
}
// app/checkout/checkout-form.tsx (Client Component)
"use client";
import { useState } from "react";
import { NumberField } from "raqam";
export function CheckoutForm({
productName,
defaultPrice,
formattedPrice,
}: {
productName: string;
defaultPrice: number;
formattedPrice: string;
}) {
const [price, setPrice] = useState<number | null>(defaultPrice);
return (
<form>
<p>Original price: {formattedPrice}</p>
<NumberField.Root
locale="en-US"
name="price"
formatOptions={{ style: "currency", currency: "USD" }}
value={price}
onChange={setPrice}
>
<NumberField.Label>Custom price</NumberField.Label>
<NumberField.Input />
<NumberField.HiddenInput />
</NumberField.Root>
<button type="submit">Checkout</button>
</form>
);
}

Import locale plugins in your root layout (Server Component imports are fine for side-effect-only modules):

app/layout.tsx
import "raqam/locales/fa";
import "raqam/locales/ar";
export default function RootLayout({ children }: { children: React.ReactNode }) {
return <html><body>{children}</body></html>;
}

raqam/server (raqam/core) is Edge Runtime compatible — it uses only Intl.NumberFormat which is available in all modern edge environments (Vercel Edge, Cloudflare Workers, Deno Deploy).

edge-function.ts
import { createFormatter } from "raqam/server";
export const runtime = "edge";
export default function handler() {
const fmt = createFormatter({ locale: "en-US" });
return new Response(fmt.format(42));
}