ComponentsForms & inputs

Formatted inputs

Business inputs that format while you type without moving the caret: money, numbers and percentages with locale separators, phone numbers with a country picker, grouped IBANs with bank lookup, and national / tax IDs with checksum rules. Values are always locale-free — a decimal string, E.164, a compact IBAN.

Input · libphonenumber-js (min, lazy) · Intl.NumberFormat lib/use-formatted-input.ts, components/formatted-input.tsx, components/currency-input.tsx, components/phone-input.tsx, components/iban-input.tsx, components/id-input.tsx, locale-data/tr/id.ts, locale-data/core/iban.tstheme · material · accent · density · font

Including 20% VAT.

Bank: Akbank

Installation

$ pnpm add @vamos-ui/react

The whole kit as one package — get updates by bumping the version. Add the styles to globals.css: @import "@vamos-ui/react/styles.css";

Usage

import { CurrencyInput, Field, IbanInput, NationalIdInput, PhoneInput } from "@vamos-ui/react";

<Field label="Amount"><CurrencyInput currency="TRY" onValueChange={(v) => setAmount(v)} /></Field>  // "1250.50"
<Field label="Mobile"><PhoneInput defaultCountry="TR" /></Field>                                   // "+905321234567"
<Field label="IBAN" error={error}><IbanInput onValidate={(r) => setError(r.message)} /></Field>    // "TR33…"
<Field label="ID number"><NationalIdInput /></Field>                                              // TCKN / YKN

Screen readers read the formatted value; the caret never jumps when separators appear or disappear, and Backspace on a separator deletes the digit before it.

Numbers and IBANs stay left-to-right (dir="ltr") on RTL pages.

Turkish rules come from @vamos-ui/react/locale-data (tr pack). Other countries plug in through registerIdRule and IbanInput rules.

Examples

Currency

Grouping follows the locale; either . or , starts the decimals (group separators are inserted for you). Paste "₺1.250,50" or "(42.00)" and it parses. The value is a decimal string padded to the currency's precision.

en-US · USD

value: "48500.00"

tr-TR · TRY — type . or , for kuruş

value: "1250000.50"

Code instead of symbol, euro after the amount in de-DE.

Number and percent

NumberInput is an ARIA spinbutton: arrow keys step, Shift steps ×10, stepper buttons are optional. PercentInput puts % where the locale does (%12,5 in Turkish).

1–250, arrow keys step, Shift ×10.

Phone

Formats for the selected country and accepts 0532…, 532…, 90532…, 0090…; typing + and a calling code switches the country. The metadata loads lazily on first mount.

Try 0532 123 45 67, 90532…, or +44 20 7183 8750.

E.164: null

IBAN

Groups of four as you type, capped at the country length, checked with mod-97 and the country's BBAN pattern. Turkish IBANs show the bank.

Turkish IBANs only. The bank appears once the bank code is typed.

National and tax IDs

Checksum rules come from locale packs (tr: TCKN, YKN, VKN). masked shows 382******50 until the field is focused.

Try 38291746550 or 99238174676.

VKN, or a sole proprietor's TCKN.

Masked until focused (KVKK).

Pattern masks

FormattedInput takes a pattern (# digit, A letter, * either) or your own formatter object.

# digit, A letter.

States

Read-only, disabled, an error from the Field, and the small size.

Bank: Garanti BBVA

Invalid IBAN checksum. Check it for typos.

In a form

Each input submits its raw value under the Field's name — no parsing on the server.

Bank: Ziraat Bankası

API

CurrencyInput

PropTypeDefault
value / defaultValue
Decimal string ("1250.50"). Never a float.
string | null—
currency
ISO 4217 code.
stringprovider currency
precision
Fraction digits (TRY 2, JPY 0, KWD 3).
numberfrom currency
min / max
Inclusive bounds as decimal strings. Checked on blur, never clamped.
string—
allowNegative
Credit notes and adjustments. - toggles the sign.
booleanfalse
currencyDisplay
How the currency mark is shown; its side comes from the locale.
"narrowSymbol" | "symbol" | "code" | "none""narrowSymbol"
symbol
Custom mark, e.g. "TL" on printed Turkish invoices.
ReactNode—
align
end lines amounts up in totals and tables.
"start" | "end""start"
onValueChange
Every change, with the locale-free value and what the field shows.
(value, meta: { valid, formatted, reason?, kind? }) => void—
validateOn
When the invalid state and onValidate kick in. Once an error shows it re-checks on every change, so it clears as the user fixes it.
"blur" | "change""blur"
onValidate
Localized message at validation moments — pass it to <Field error>.
(result: { valid, reason?, message? }) => void—
name
Submits the raw value through a hidden input (the visible input keeps the formatted text). Falls back to the Field's name.
string—
mono
Monospace tabular digits (default on for IBAN and ID inputs).
boolean—
size · variant · leading · trailing · disabled · readOnly · invalid
Same box as Input: density-aware sizes, ghost/nav variants, adornments, Field wiring.
Input props—

NumberInput · PercentInput

PropTypeDefault
value / defaultValue
A JS number (PercentInput: percent units, 12.5 = 12.5%).
number | null—
min / max / step
Bounds (checked on blur) and the arrow / stepper increment.
numberstep 1
precision
Max fraction digits; 0 = whole numbers. PercentInput default 2.
number—
stepper
Labelled − / + buttons at the end.
booleanfalse
grouping
Locale grouping separators.
booleantrue

PhoneInput

PropTypeDefault
value / defaultValue
E.164 ("+905321234567"); null while the number is incomplete.
string | null—
country / defaultCountry / onCountryChange
Selected country (ISO 3166-1 alpha-2).
stringTR for tr locales, else the locale region
countries
Limit the picker.
string[]—
preferredCountries
Listed first under "Suggested".
string[]—
display
How a complete number is shown after blur.
"national" | "international""national"
hideCountry
Single-country forms.
booleanfalse
preloadPhoneInput()
Load the metadata early (e.g. on hover of "Add contact").
() => Promise—

IbanInput

PropTypeDefault
value / defaultValue
Electronic format, uppercase, no spaces.
string | null—
countries
Allowed countries; others fail with reason "country".
string[]—
rules
Per-country length, BBAN pattern and bank lookup from locale packs.
IbanCountryRule[][trIbanRule]
showBank
Bank name next to the number.
boolean | (iban) => string | undefinedtrue

IdInput · NationalIdInput · TaxIdInput

PropTypeDefault
kind
A registered rule or a rule object. meta.kind reports sub-kinds (tr-ykn).
"tr-tckn" | "tr-vkn" | "tr-tax-id" | string | IdInputRuletr-tckn / tr-tax-id
masked
Privacy mask while not focused (123******01).
booleanfalse
registerIdRule(rule)
Add rules from locale packs or your own (charset: digits | alphanumeric).
(rule: IdInputRule) => void—

FormattedInput

PropTypeDefault
mask
# digit, A letter, * letter or digit; other characters are literals inserted for you.
string—
formatter
Your own rules on the same caret-preserving engine (useFormattedInput).
{ accept, format, parse, toDisplay?, formatOnBlur?, validate? }—
uppercase
Uppercase letters as typed.
boolean—
valueFormat
Raw value with or without literals.
"unmasked" | "masked""unmasked"