CurrencyInput
A monetary input that stores its value in minor units (cents) and formats it for the active locale.
Playground
<CurrencyInput
label="Price"
locale="de-DE"
/>01 Examples
Follows the active locale
<script>
import { CurrencyInput } from '@urbicon-ui/blocks';
let priceCents = $state(1234_56); // grouped per the active locale
</script>
<CurrencyInput label="Price" bind:value={priceCents} />USD with prefix symbol
<CurrencyInput
label="Amount"
bind:value={amountCents}
locale="en-US"
currency="USD"
symbolPosition="prefix"
/>JPY: zero decimal precision
<CurrencyInput
bind:value={yen}
locale="ja-JP"
currency="JPY"
precision={0}
/>02 Working with major units
CurrencyInput stores values in minor units (cents) so summing,
sorting, and persisting amounts stay free of floating-point drift. When integrating with an
API or datastore that uses major-unit floats (e.g. 1234.56 for €1.234,56), use
the exported centsToMajor / majorToCents helpers at the boundary, and
keep the in-memory value in cents.
Cents in, major units out
Stored as cents: 123456 · Exported as major: 1234.56
<script>
import { CurrencyInput, centsToMajor, majorToCents } from '@urbicon-ui/blocks';
// Major-unit float arrives from an external API.
const apiAmount = 1234.56;
let cents = $state(majorToCents(apiAmount)); // 123456
const exportedAsMajor = $derived(centsToMajor(cents)); // 1234.56
</script>
<CurrencyInput label="Price" bind:value={cents} />
<p>Major: {exportedAsMajor}</p>Float-precision caveat: a major-unit value that has already lost precision
before reaching majorToCents (e.g. 0.1 + 0.2) cannot be recovered.
For values that must round-trip exactly across system boundaries, transport them as minor-unit
integers (or as strings) instead of major-unit floats.
03 Customization
CurrencyInput builds on <Input>, so its InputProps (label, helper, error, slotClasses, …) apply here too. The cents-based value, locale, currency, symbolPosition, and precision props add the locale-aware behaviour.
Use symbolPosition="none" for headless numeric editing where you want the locale formatting
(grouping / decimal separator) without the currency symbol.
04 Accessibility
Inherited from Input
Inherits aria-invalid / aria-describedby wiring from the
underlying <Input> via the label, error, and helper props.
Numeric keyboard
Sets inputmode="decimal" so mobile keyboards open the numeric pad with a decimal
separator.
The caret stays where the user put it
The field re-formats on every keystroke, so grouping separators appear and disappear under the caret. It is carried across as a digit position rather than a character offset, and the fraction is a fixed row of slots: deleting a cent digit zeroes it instead of pulling the separator along.
05 API Reference
34 propsProp | Type | Default | Description | |
|---|---|---|---|---|
autoComplete inherited | string | — | HTML autocomplete hint for browser autofill. | |
children inherited | Snippet | — | Snippet content rendered below the input for advanced layouts. | |
class inherited | string | — | Extra classes merged onto the root wrapper element. | |
clearable inherited | boolean | false | Show a clear button when the input has a value.
Press Escape or click the button to clear. Fires onClear after clearing. | |
currency | string | 'EUR' | ISO-4217 currency code. Determines the symbol when
CurrencyInputProps.symbolPosition is 'prefix' or 'suffix'. | |
disabled inherited | boolean | false | disabled property | |
error inherited | string | — | Error message below the input. When set, overrides helper and
forces danger border styling regardless of intent. | |
helper inherited | string | — | Helper text below the input — hidden when error is present. | |
label inherited | string | — | Label text displayed above the input, auto-linked via for/id. | |
leftIcon inherited | Snippet | — | Icon snippet rendered on the left side of the input field. | |
leftIconAriaLabel inherited | string | — | Accessible label for the clickable left icon button. Required when
onLeftIconClick is set so screen-reader users hear a name for the
button (icons inside are aria-hidden). | |
locale | string | 'auto' | BCP 47 locale used for formatting (Intl.NumberFormat). Controls the
grouping separator (. vs ,) and decimal separator. Defaults to
'auto', which follows the active <I18nProvider> locale — SSR-safe
(server and client resolve the same locale, no hydration flash) and
consistent with the rest of the library's number formatting. Falls back
to the base locale (en) when no provider is mounted. Pass an explicit
BCP 47 string (e.g. 'de-DE', 'ja-JP') to override. currency is
intentionally **not** auto-detected, since it is orthogonal to locale
(a de-CH user may still bill in EUR). | |
mint | InputProps['mint'] | 'none' | Micro-interaction preset forwarded to the inner Input. Redeclared from InputProps so the inheritance is a documented contract rather than an accident of the Omit list. | |
name | string | — | Shared name for native form submission. When set, a hidden input
is rendered carrying the integer minor-unit value (matching
CurrencyInputProps.value) — never the locale-formatted display
string. The visible Input itself stays unnamed so the formatted text
is not submitted alongside.
Empty / null values submit as "" so the field still appears in
the FormData payload (consumers can disambiguate "untouched" via
server-side schema parsing). | |
onLeftIconClick inherited | () => void | — | When provided, the left icon becomes a clickable button. | |
onRightIconClick inherited | () => void | — | When provided, the right icon becomes a clickable button. | |
onValueChange | (cents: number | null) => void | — | Fires whenever the parsed value changes. Receives the new value in minor units (or null). | |
persistDebounceMs inherited | number | 300 | Debounce interval (ms) for storage writes. | |
persistKey inherited | string | — | Key for persisting the input value to storage. | |
persistNamespace inherited | string | — | Namespace (e.g. user id) to scope the persist key. | |
persistStorage inherited | localStoragesessionStorage | 'localStorage' | Storage backend for persistence. | |
persistVersion inherited | number | 1 | Version stamp included in the storage key. | |
precision | number | 2 | Number of fractional digits stored in CurrencyInputProps.value.
For most currencies 2; for JPY use 0, for BHD/KWD use 3. | |
preset | string | — | Apply a named preset registered via <BlocksProvider presets={{ CurrencyInput: {...} }}>.
Resolved against the **CurrencyInput** key, not Input: a preset written for
the money field would otherwise style every text field under the provider.
defaults.Input still applies — the resolved preset reaches Input as
instance slotClasses, so it wins over the provider's input-wide defaults
and loses to slotClasses / class written on this component.
A preset's overrides rules are matched against what you wrote here plus
Input's own variant defaults; an axis Input derives or coerces for itself
(tier, messageType, error, hasRightIcon, iconPosition) can match
the wrong state — #360. | |
readonly inherited | boolean | false | readonly property | |
required inherited | boolean | false | Adds a required asterisk to the label and sets the native required attribute. | |
rightIcon inherited | Snippet | — | Icon snippet rendered on the right side of the input field. | |
rightIconAriaLabel inherited | string | — | Accessible label for the clickable right icon button. Required when
onRightIconClick is set so screen-reader users hear a name for the
button (icons inside are aria-hidden). | |
slotClasses inherited | Partial<Record<InputSlots, string>> | — | Per-slot class overrides merged with tv() styles. Slots: wrapper (root —
what class also targets) | container | base (the <input> element) |
label | requiredMark | message | iconContainer | iconButton | iconDecoration. | |
symbolPosition | CurrencySymbolPosition | 'suffix' | Where the currency symbol is rendered as a static adornment. The symbol
is shown in the input's left or right icon slot (always visible, including
while focused) — it is never embedded in the editable text. Use 'none'
for headless numeric editing (no symbol shown at all). | |
unstyled inherited | boolean | — | Remove all default tv() classes — only user-provided classes apply. | |
value | number | null | null | Current monetary value in **minor units** (e.g. cents).
Use null for "no value entered yet"; the input renders empty. |
06 Types
Local type definitions used by this component.
Name | Kind | Category | Used by | Description | |
|---|---|---|---|---|---|
CurrencySymbolPosition | type | helper | 1 | Where the currency symbol appears relative to the input field.
The symbol is rendered as a static adornment in the input's left or right
icon slot — never embedded in the editable text — so it stays visible during
editing and never doubles up with the locale's own currency formatting.
'none' suppresses the symbol entirely (e.g. for pure numeric editing). | |
CurrencyInputProps | interface | props | 0 | — | |
InputProps | interface | props | 0 | — | |
InputVariants | type | variant | 0 | — | |
MintProp | type | helper | 2 | — | |
InputSlots | type | variant | 0 | Slot names derived from the tv() config above — single source of truth for slotClasses. | |
VariantProps | type | helper | 0 | — | |
MintName | type | helper | 0 | A mint name: a built-in (autocompleted), 'none' to disable, or any
consumer-registered name. (string & {}) keeps the registry open — a
custom name still type-checks, it just isn't suggested. A typo therefore
also still compiles (it resolves like an unregistered custom name and
warns at runtime); the union buys completion and docs, not validation. | |
MintConfig | interface | helper | 0 | — | |
SlotNames | type | helper | 0 | Extracts the slot-name union from a slotted tv() config function — the
companion to VariantProps. The slot-mode overload returns
(props?) => { [K in keyof S]: SlotFn }, so keyof ReturnType<T> is exactly
the set of slot names a component declares in tv({ slots: … }).
Use it to type a component's slotClasses prop from the single source of
truth (its *.variants.ts) instead of hand-maintaining a parallel union
that silently drifts when a slot is added or renamed: |
07 Installation
Import
import { CurrencyInput } from '@urbicon-ui/blocks';