Input Currency
Currency field with symbol addon, locale-aware decimals, and normalized values on commit.
Overview
InputCurrency composes Input Group: a text input plus an inline-start or inline-end
addon for the currency symbol, using currency and Intl. While typing, the field shows only
plain decimal text (no symbol inside the input). Blur or Enter reformats the numeric
fragment with Intl, and onValueChange emits a normalized decimal string (or null when
empty/invalid). Use onValueChange for committed amounts; use onChange from the underlying input
only when you need the visible string while typing. See Usage.
Key bindings
| Key | Behavior |
|---|---|
| ⌫ | Deletes before the caret (native); input is re-sanitized on change. |
| ⌦ | Deletes after the caret (native). |
| ←/→ | Moves the caret; does not increment or decrement the amount. |
| ↵ | Commits: applies Intl formatting and fires onValueChange when the normalized value changes. |
| ⇥ | Moves focus in normal tab order. |
blur | Commits when leaving the field (same commit path as ↵). |
Custom props
| Prop | Type | Description |
|---|---|---|
currency | string | Required. ISO 4217 code (USD, EUR, …). |
locale | string | undefined | Passed to Intl. Omit for the runtime default locale. |
value | string | number | Controlled value. Strings control draft text; numbers control numeric state. |
onValueChange | (value: string | null) => void | Fires on commit when the normalized value changes (see Overview). |
min | number | Optional minimum clamp. Ignored if min > max. |
max | number | Optional maximum clamp. Ignored if min > max. |
size | 'xs' | 'sm' | 'default' | 'lg' | Optional. Passed to the root InputGroup: shell height matches other inputs; the text field and symbol addon scale with the group. |
Install
Configure your registry in components.json:
{
"registries": {
"@uxio": "https://ui.uxio.dev/r/styles/{style}/{name}.json"
}
}Then run:
npx shadcn@latest add @uxio/input-currencyThis pulls input-group as a registry dependency. The correct variant (Base UI or Radix) follows
your components.json style.
Usage
String-controlled
import * as React from "react"
import { InputCurrency } from "@/components/ui/input-currency"
export function PriceField() {
const [text, setText] = React.useState("")
const [committed, setCommitted] = React.useState<string | null>(null)
return (
<InputCurrency
currency="USD"
locale="en-US"
value={text}
min={0}
onChange={(event) => setText(event.target.value)}
onValueChange={setCommitted}
/>
)
}Number-controlled
import * as React from "react"
import { InputCurrency } from "@/components/ui/input-currency"
export function AmountField() {
const [value, setValue] = React.useState(12.5)
return (
<InputCurrency
currency="EUR"
locale="de-DE"
value={value}
onChange={(event) => {
console.log("visible text", event.target.value)
}}
onValueChange={(next) => {
if (next !== null) setValue(Number(next))
}}
/>
)
}Examples
Default
Visible text (onChange): —
Committed value (onValueChange): —
Size variants
Use size so the field aligns with InputGroup and Input dimensions. The inner InputGroupInput inherits the group size when you omit its own size.
xs
sm
default
lg