Caret stable
Type, paste, delete from the middle. The caret never jumps to the end.
A reactive input component library for Svelte 5 with caret-stable formatting, two-way binding, and Intl-grade locale support. Four components, 15+ masks, zero glue code.
Four components — NumericFormat, PatternFormat, NumericText, and PatternText — cover the entire surface. The rest
is just options.
Type, paste, delete from the middle. The caret never jumps to the end.
bind:value works exactly like a native input. Programmatic updates re-format on the spot.
Built on Intl.NumberFormat. Switch between en-US, de-DE, ja-JP without re-mounting.
NumericFormat covers the three styles you actually use, with precision and grouping.
Phone, credit card, SSN, dates, IPs, MAC — drop in MaskPatterns.* and ship.
Built for runes ($state, $derived). No legacy stores, no compat shims.
Strict types throughout. Autocomplete on every option, every mask.
NumericText and PatternText render formatted values server-side with no DOM.
Pass a formatStyle, pick a precision, bind a
value. The component takes care of grouping, separators, and currency
symbols.
# for digits, A for letters, * for alphanumerics, anything else literal.
15+ ready-made patterns from MaskPatterns.
If you've used react-number-format, this
will feel immediately familiar — without the React.
<script lang="ts"> import { NumericFormat, NumberFormatStyle } from 'svelte-number-format' let price = $state<number | null>(99.99)</script> <NumericFormat bind:value={price} locale="en-US" options={{ formatStyle: NumberFormatStyle.Currency, currency: 'USD', precision: 2 }}/><!-- User sees: $99.99 -->
<script lang="ts"> import { PatternFormat, MaskPatterns } from 'svelte-number-format' let phone = $state<string | null>(null)</script> <PatternFormat bind:value={phone} format={MaskPatterns.PHONE_US} placeholder="(555) 123-4567"/><!-- Display: (555) 123-4567 · raw: "5551234567" -->
The short versions. The README has the long ones, with code.
Install svelte-number-format and use NumericFormat with options={{ formatStyle: NumberFormatStyle.Currency, currency: 'USD', precision: 2 }}. It formats as you type, keeps the caret stable, and bind:value gives you the plain number — not the formatted string.
No. The library is built on Svelte 5 runes and declares a svelte@^5 peer dependency. There is no Svelte 4 build.
PatternFormat is 4.6 kB gzipped with zero runtime dependencies; NumericFormat is ~8.1 kB gzipped including its only dependency, intl-number-input. Subpath imports let you load only what you use. Numbers are gzipped package source, pinned by size-limit budgets in CI.
Same NumericFormat and PatternFormat component names, same onValueChange payload, and the same # pattern token — plus A/* and custom regex tokens that react-number-format doesn't have. The API is idiomatic Svelte: bind:value instead of controlled-state wiring, and locale-driven separators instead of manual thousandSeparator/prefix props.
svelte-currency-input is a focused, well-made currency-only field — if that's all you need, it's a fine choice. svelte-number-format covers currency plus percent and decimal inputs, pattern masks for phone, credit card and dates, display-only components, and mirrors react-number-format's API.
Yes — both inputs expose plain bind:value, so they drop into any Svelte form library. The demos on this site show worked integrations with Superforms, Formsnap, Felte, and Zod validation.
Yes. No browser APIs run at module evaluation; the default locale resolves lazily — navigator.language on the client, 'en-US' on the server. The input components render on the server and format on hydration; NumericText and PatternText render fully formatted markup during SSR.
Yes — pass a name prop and a hidden input carries the raw value (1234.56, not $1,234.56) into FormData, form actions, and SvelteKit remote functions. The visible formatted input stays unnamed.
MIT licensed. ~8.1 kB gzipped. Zero config. Read the docs →
Live integration demos: Superforms · Formsnap · Felte · Zod 3 × 4
$npm i svelte-number-format