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

@rxova/react-intl-currency-input

v1.0.3

Published

Localized currency input for React. Zero dependencies, no cursor bugs, correct in every Intl locale — including the ones other libraries get wrong.

Readme

Localized currency input for React. Zero dependencies, no cursor bugs, and correct in every Intl locale — including the ones other libraries get wrong.

npm install @rxova/react-intl-currency-input

Requires React and React DOM 18 or newer. Both are peer dependencies; there are no bundled runtime dependencies and no stylesheet to import.

📖 Documentation & live examples → — the locale matrix, formatting options, form recipes, and migration, with live editable examples you can change in the browser: locales, currencies, precision, digits, negatives and keyboard stepping.

  • Intl owns the formatting — group separator, decimal separator, symbol placement, fraction digits, and grouping rules are all read from Intl.NumberFormat, never reconstructed
  • No cursor bugs — formats as you type with the caret anchored to the digit you typed, so it never jumps (or opt into formatMode="blur" for the simplest possible field)
  • Number in, number out — your value is a number, not a formatted string to parse back
  • Useful input controls — opt-in ArrowUp/ArrowDown stepping and raw-input transformation
  • Correct where others break — bg-BG's "space only above 9999", fr-FR's narrow no-break space, ja-JP's zero decimals, KWD's three, ar-EG's native digits, hi-IN's lakh grouping, de-CH's apostrophe
  • Zero runtime dependencies, ~3.2 kB brotli, no stylesheet to import
  • Form-ready — first-class React Hook Form / Formik / React Final Form / TanStack Form recipes

One value, eight languages

The same 1234567.89, formatted by the same code. The language decides the separators, symbol side and digits; the currency decides the fraction count. None of these is a special case.

The Bulgarian quirk, live

5000 has no group separator; the moment the value crosses 10000 a (non-breaking) space appears — straight from CLDR, no branch in our code.

Same currency, nine languages

One US-dollar amount, rendered for nine locales — watch the symbol jump sides and the grouping change (including Indian lakh grouping on $12,34,567.5).

Quick start

import { useState } from "react";
import { CurrencyInput } from "@rxova/react-intl-currency-input";

function Price() {
  const [value, setValue] = useState<number | null>(50000);
  return <CurrencyInput locale="bg-BG" currency="EUR" value={value} onChange={setValue} />;
}

5000 stays 5000 €, but 50000 becomes 50 000 € — the Bulgarian rule that only groups above 9999, applied for free because Intl owns the formatting.

How it works

An HTML <input> has no type="currency". Rolling your own means handling a different group separator, decimal separator, symbol position, and fraction-digit count for every locale — and keeping the caret from jumping as separators appear while typing.

This library solves both:

  • Every locale fact is read from Intl.NumberFormat, never reconstructed, so the tricky locales are correct by construction, not special-cased.
  • By default it formats as you type (formatMode="live") and keeps the caret in place by anchoring it to the digit you typed rather than the character position — the group separators that come and go never move it.

Prefer the simplest possible field? formatMode="blur" shows a plain number while focused and formats only on blur, with no caret management at all.

API

<CurrencyInput>

| Prop | Type | Default | Notes | | ----------------------- | ------------------------------------------------ | ---------------- | -------------------------------------------------------- | | locale | string | runtime default | BCP-47, e.g. 'bg-BG'. Wins over language/country. | | language + country | string | — | Combined into ${language}-${country} when no locale. | | currency | string (required) | — | ISO-4217, e.g. 'EUR'. | | value | number \| null | — | Controlled. null renders an empty field. | | defaultValue | number \| null | null | Uncontrolled initial value. | | onChange | (value, meta) => void | — | value is a number \| null, not a DOM event. | | onValueChange | (value, meta) => void | — | Deprecated alias for onChange. | | onNativeChange | ChangeEventHandler<HTMLInputElement> | — | Forwarded DOM handler; runs after the internal one. | | maximumFractionDigits | number | currency default | JPY 0, EUR 2, KWD 3. | | minimumFractionDigits | number | 0 | Set to 2 to force trailing zeros. | | currencyDisplay | 'symbol' \| 'narrowSymbol' \| 'code' \| 'name' | 'symbol' | Passed to Intl. | | numberingSystem | string | locale default | e.g. 'latn' to force ASCII digits. | | allowNegative | boolean | false | Enables refunds / adjustments. | | formatMode | 'live' \| 'blur' | 'live' | 'live' formats as you type; 'blur' on blur only. | | step | number | — | ArrowUp/ArrowDown increment. | | transformRawValue | (raw: string) => string | — | Runs before locale-aware sanitization. | | invalid | boolean | — | Sets aria-invalid + data-invalid. |

All other props (id, name, className, style, aria-*, event handlers) forward to the underlying <input>. ref forwards to the input.

Styling

The package renders a native <input> and ships no stylesheet. Style it with className, style, CSS Modules, CSS-in-JS, or utility classes. The invalid prop supplies both the accessible aria-invalid state and a [data-invalid] selector:

<CurrencyInput className="money-input" locale="bg-BG" currency="EUR" invalid={hasError} />
.money-input {
  inline-size: 100%;
  padding: 0.625rem 0.75rem;
  border: 1px solid #94a3b8;
  border-radius: 0.5rem;
  font: inherit;
  font-variant-numeric: tabular-nums;
  text-align: end;
}

.money-input:focus-visible {
  border-color: #2563eb;
  outline: 3px solid rgb(37 99 235 / 25%);
}

.money-input[data-invalid] {
  border-color: #b42318;
}

data-* attributes

These are public API, covered by semver. This component renders a single element, so the root is the input — hence one selector hook where the composite inputs in the suite carry several.

| Attribute | On | Meaning | | ----------------------- | --------- | -------------------------- | | data-rx-currency-root | <input> | Always present | | data-invalid | <input> | Mirrors the invalid prop |

It defines no CSS custom properties: there is nothing painted on top of the input to configure, so className and ordinary CSS reach everything.

See the complete styling guide for labeled fields, CSS Modules, Tailwind, wrapper adornments, design-system variants, and RTL-safe layout.

useCurrencyInput(options)

The headless core. Returns { inputProps, value, display, focused, setValue, format, parse, … } — spread inputProps onto your own <input>.

import { useState } from "react";
import { TextField } from "@mui/material";
import { useCurrencyInput } from "@rxova/react-intl-currency-input";

function MuiPrice() {
  const [value, setValue] = useState<number | null>(null);
  const currency = useCurrencyInput({
    locale: "de-DE",
    currency: "EUR",
    value,
    onChange: setValue,
  });
  const { inputMode, ...textFieldProps } = currency.inputProps;

  return <TextField {...textFieldProps} label="Price" slotProps={{ htmlInput: { inputMode } }} />;
}

See the styling guide for shadcn/ui, Radix Themes, MUI, Chakra UI, Mantine and Ant Design.

currencyForCountry(code)

Best-effort ISO-3166 → ISO-4217 lookup for convenience. Prefer passing currency explicitly.

Forms

onChange emits a number, so use a controlled adapter (RHF Controller, Formik useField, etc.). See the form-library guide for React Hook Form, Formik, React Final Form and TanStack Form.

UI-library recipes

The docs contain maintained examples for shadcn/ui, Radix Themes, Material UI, Chakra UI, Mantine and Ant Design. Copy a ready-labelled wrapper with:

npx shadcn@latest add https://rxova.dev/packages/react-inputs/r/currency-field.json

Open the recipes.

Part of rxova

Part of the rxova headless React inputs suite — install the whole set from @rxova/react-inputs, or read the full generated API reference for this package.

License

MIT © rxova