npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

persian-number-input

v5.0.3

Published

React component for Persian, Indic, or English localized number input with customizable digit grouping

Readme

Persian Number Input — React Component for Farsi, Arabic & RTL Number Formatting

فارسی | English

A lightweight React library for Persian (Farsi) and Arabic number inputs with automatic digit conversion, thousand-separator formatting, decimal precision, and full RTL support. No more manual conversion — users type in their native digits, your form receives clean English numbers.

npm version npm downloads bundle size license

🚀 Live Demo


Why Persian Number Input?

Building forms for Persian or Arabic-speaking users comes with real challenges:

  • Users naturally type ۱۲۳۴ — but your API expects 1234
  • Standard <input type="number"> doesn't support Persian or Arabic digits at all
  • Cursor jumps erratically when formatting thousand separators on the fly
  • Decimal handling differs across locales (٫ vs .)
  • RTL inputs need right-aligned text and correct suffix placement

This library solves all of these automatically:

User types:   ۱۲۳۴۵۶۷
Displayed as: ۱,۲۳۴,۵۶۷
Form receives: "1234567"

✨ Features

  • 🔢 Automatic digit conversion — Persian (۰-۹) and Arabic (٠-٩) to English and back

  • 🌍 Multi-localefa (Farsi/Persian), ar (Arabic), en (English)

  • 📊 Thousand separator formatting — customizable separator character and group size

  • 💰 Currency-ready — add suffix (e.g. تومان, ریال, ر.س) as a visual-only overlay, and custom decimal separator

  • ~1KB gzipped — zero extra dependencies, pure TypeScript

  • 🎯 TypeScript — full type definitions included

  • 🔄 Cursor-position preservation — no jump on re-format

  • 📋 Smart paste handling — pasted content is sanitized and auto-converted

  • Min/max validation — soft enforcement with aria-invalid and automatic clamp on blur

  • Accessible — follows WCAG input best practices


📦 Installation

npm install persian-number-input
# or
yarn add persian-number-input
# or
pnpm add persian-number-input

Requirements: React 16.8+, works with Next.js, Vite, CRA, and any React-based framework.


🎯 Quick Start

import { PersianNumberInput } from "persian-number-input";

function App() {
  return (
    <PersianNumberInput
      initialValue={1234567}
      locale="fa"
      onValueChange={(value) => console.log(value)} // "1234567"
    />
  );
}

Output displayed to user: ۱,۲۳۴,۵۶۷


📚 Usage Examples

Persian Toman currency input

<PersianNumberInput
  initialValue={5000000}
  locale="fa"
  suffix="تومان"
  separatorCount={3}
  separatorChar=","
  onValueChange={(value) => console.log(value)}
/>

Displayed to user: ۵,۰۰۰,۰۰۰ with تومان rendered as a visual-only overlay next to the input.

The suffix is rendered outside the input's value — it never interferes with cursor position, selection, or backspace behavior.


Arabic locale (SAR)

<PersianNumberInput
  initialValue={987654}
  locale="ar"
  separatorChar=","
  suffix="ر.س"
  onValueChange={(value) => console.log(value)}
/>

Displayed to user: ٩٨٧,٦٥٤ with ر.س rendered as a visual-only overlay.


Decimal numbers with custom separator

<PersianNumberInput
  initialValue={1234.56}
  locale="fa"
  maxDecimals={2}
  decimalChar="٫"
  separatorChar=","
  onValueChange={(value) => console.log(value)}
/>

Displayed: ۱,۲۳۴٫۵۶


Price input with min/max validation

<PersianNumberInput
  initialValue={0}
  locale="fa"
  min={0}
  max={999999999}
  suffix="ریال"
  showZero={true}
  onValueChange={(value) => console.log(value)}
/>

Displayed to user: ۰ with ریال rendered as visual-only overlay.


With React Hook Form

import { useForm, Controller } from "react-hook-form";
import { PersianNumberInput } from "persian-number-input";

function ProductForm() {
  const { control, handleSubmit } = useForm();

  return (
    <form onSubmit={handleSubmit(console.log)}>
      <Controller
        name="price"
        control={control}
        rules={{ required: true }}
        render={({ field }) => (
          <PersianNumberInput
            locale="fa"
            suffix="تومان"
            onValueChange={field.onChange}
            initialValue={field.value}
          />
        )}
      />
      <button type="submit">ثبت</button>
    </form>
  );
}

Using the hook directly (advanced)

import { usePersianNumberInput } from "persian-number-input";

function CustomInput() {
  const { value, onChange, onBlur, rawValue, isInvalid } =
    usePersianNumberInput({
      initialValue: 1000,
      locale: "fa",
      suffix: "تومان",
      maxDecimals: 2,
      min: 0,
      max: 1000000,
      onValueChange: (val) => console.log("English value:", val),
    });

  return (
    <div>
      <div style={{ position: "relative", display: "inline-flex", alignItems: "center" }}>
        <input
          type="text"
          value={value}
          onChange={onChange}
          onBlur={onBlur}
          onPaste={onPaste}
          dir="ltr"
          aria-invalid={isInvalid || undefined}
        />
        <span
          style={{
            position: "absolute",
            pointerEvents: "none",
            right: 0,
            top: 0,
            bottom: 0,
            display: "flex",
            alignItems: "center",
          }}
        >
          تومان
        </span>
      </div>
      <p>Raw value: {rawValue}</p>
    </div>
  );
}

When using a suffix with the hook, wrap the <input> in a relative container and render the suffix as an absolutely-positioned <span> with pointer-events: none. This ensures the suffix never interferes with cursor positioning or backspace behavior. Add paddingRight to the input to prevent text from overlapping the suffix overlay.


🛠️ API Reference

PersianNumberInput props

| Prop | Type | Default | Description | | ---------------- | -------------------------------------- | ----------- | ---------------------------------------------------- | | initialValue | number \| string | undefined | Initial value of the input | | locale | "fa" \| "ar" \| "en" | "fa" | Digit locale — Farsi, Arabic, or English | | separatorCount | number | 3 | Digits per group (3 = thousand separator) | | separatorChar | string | "," | Thousand separator character | | decimalChar | string | Auto | Decimal separator (٫ for fa, . for en) | | suffix | string | undefined | Visual-only suffix rendered outside the input value — never affects cursor, selection, or backspace (e.g. تومان, ریال) | | maxDecimals | number | undefined | Maximum allowed decimal places | | min | number | undefined | Minimum allowed value | | max | number | undefined | Maximum allowed value — soft validation (marks invalid, clamps on blur) | | showZero | boolean | false | Display 0 instead of empty when value is zero | | onValueChange | (value: string \| undefined) => void | undefined | Fires on change — always returns English digits |

All standard HTML <input> props are also supported, including onChange, onKeyDown, onPaste, className, style, placeholder, dir, and disabled.


Utility functions

transformNumber(rawValue, options)

Format any number string according to locale and options.

import { transformNumber } from "persian-number-input";

transformNumber("1234567.89", {
  locale: "fa",
  separatorCount: 3,
  separatorChar: ",",
  maxDecimals: 2,
});
// → "۱,۲۳۴,۵۶۷٫۸۹"

Note: suffix is a UI-only feature of the component and hook. The transformNumber utility does not append suffix — use the PersianNumberInput component or usePersianNumberInput hook for suffix rendering.

toEnglishDigits(str, decimalChar?)

Convert Persian or Arabic digits to English.

import { toEnglishDigits } from "persian-number-input";

toEnglishDigits("۱۲۳۴"); // "1234"
toEnglishDigits("٩٨٧٦"); // "9876"

toLocalizedDigits(numStr, locale)

Convert English digits to the target locale.

import { toLocalizedDigits } from "persian-number-input";

toLocalizedDigits("1234", "fa"); // "۱۲۳۴"
toLocalizedDigits("5678", "ar"); // "٥٦٧٨"

sanitizeNumericInput(value, maxDecimals?, decimalChar?)

Strip non-numeric characters and enforce decimal limits.

import { sanitizeNumericInput } from "persian-number-input";

sanitizeNumericInput("۱۲۳abc۴۵۶", 2); // "123456"
sanitizeNumericInput("12.345.67", 2);  // "12.34"

stripNonNumeric(str)

Remove all non-digit and non-dot characters.

import { stripNonNumeric } from "persian-number-input";

stripNonNumeric("12abc34.56xyz"); // "1234.56"

normalizeDecimals(str)

Keep only the first decimal point.

import { normalizeDecimals } from "persian-number-input";

normalizeDecimals("12.34.56"); // "12.3456"

stripLeadingZeros(str)

Strip leading zeros from a numeric string.

import { stripLeadingZeros } from "persian-number-input";

stripLeadingZeros("001234.56"); // "1234.56"

applyDecimalPrecision(str, maxDecimals?)

Truncate fractional part to the specified decimal places.

import { applyDecimalPrecision } from "persian-number-input";

applyDecimalPrecision("1234.5678", 2); // "1234.56"

roundToDecimals(value, maxDecimals?)

Alias for applyDecimalPrecision — truncate fractional part.

import { roundToDecimals } from "persian-number-input";

roundToDecimals("1234.5678", 2); // "1234.56"

🎨 Styling

The component accepts all standard input props including className and style:

<PersianNumberInput
  locale="fa"
  className="w-full px-4 py-3 text-lg border-2 border-indigo-500 rounded-lg text-right focus:outline-none focus:ring-2 focus:ring-indigo-600"
/>

For RTL layouts, add dir="rtl" to the wrapper or style={{ textAlign: "right" }} on the input.


📊 Bundle Size

persian-number-input: ~1KB (minified + gzipped)

One of the smallest solutions for Persian/Arabic number input in the React ecosystem.


🏆 Comparison

| Feature | persian-number-input | Native <input> | Other libraries | | ------------------------------ | :------------------: | :--------------: | :-------------: | | Persian & Arabic digit input | ✅ | ❌ | ⚠️ Partial | | Cursor preservation on format | ✅ | ❌ | ⚠️ Often buggy | | Multi-locale (fa / ar / en) | ✅ | ❌ | ❌ | | Thousand separator formatting | ✅ | ❌ | ⚠️ Limited | | Decimal precision control | ✅ | ❌ | ⚠️ Limited | | Min/max validation | ✅ | Partial | ⚠️ Varies | | TypeScript support | ✅ | ✅ | ⚠️ Varies | | Bundle size | 🟢 ~1KB | 🟢 Native | 🔴 5–30KB | | Zero peer dependencies | ✅ | ✅ | ❌ |


❓ FAQ

Q: Does this work with Next.js and Server Components?
A: Yes. The component is a client component — add "use client" at the top of your file when using it inside a Next.js App Router layout.

Q: How do I get the numeric value back in English for my API?
A: The onValueChange callback always returns the raw English digit string (e.g. "1234567"), regardless of which locale is displayed.

Q: Can I use this inside a form with native onSubmit?
A: Use onValueChange to store the value in local state, then read from state on submit. The component also supports React Hook Form via the Controller pattern (see example above).

Q: Does it handle right-to-left text direction automatically?
A: The component formats numbers correctly for RTL — for full RTL layout, set dir="rtl" on your container or style={{ textAlign: "right" }} on the input.

Q: What's the difference between locale="fa" and locale="ar"?
A: fa uses Persian digits (۰–۹) and the ٫ decimal separator. ar uses Eastern Arabic digits (٠–٩). Both return English digits to onValueChange.

Q: Is there a maximum number size?
A: The library uses string-based numeric comparison for validation, so it handles arbitrarily large and precise numbers without floating-point artifacts.


🤝 Contributing

Contributions are welcome!

  1. Fork the repository
  2. Create your feature branch: git checkout -b feature/your-feature
  3. Commit your changes: git commit -m 'Add your feature'
  4. Push: git push origin feature/your-feature
  5. Open a Pull Request

Please open an issue first for major changes so we can discuss the approach.


📄 License

MIT © Javad Sharifi


📞 Support


Made with ❤️ for Persian and Arabic developer communities