Inputs

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

KeyBehavior
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.
blurCommits when leaving the field (same commit path as ).

Custom props

PropTypeDescription
currencystringRequired. ISO 4217 code (USD, EUR, …).
localestring | undefinedPassed to Intl. Omit for the runtime default locale.
valuestring | numberControlled value. Strings control draft text; numbers control numeric state.
onValueChange(value: string | null) => voidFires on commit when the normalized value changes (see Overview).
minnumberOptional minimum clamp. Ignored if min > max.
maxnumberOptional 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-currency

This 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