CurrencyInput

A currency input component with locale-based formatting and real-time editing

Installation

pnpm add @wandercom/design-system-web

Usage

import { CurrencyInput } from '@wandercom/design-system-web';

export function Example() {
  const [value, setValue] = useState<number | null>(1234.56);

  return (
    <CurrencyInput
      value={value}
      currency="$"
      onChange={setValue}
    />
  );
}

Examples

Default

Basic currency input with USD dollar symbol.

Loading example...
const [value, setValue] = useState<number | null>(1234.56);

<CurrencyInput
  value={value}
  currency="$"
  onChange={setValue}
  placeholder="0.00"
/>

Sizes

Currency input supports both default and small sizes.

Loading example...
const [defaultValue, setDefaultValue] = useState<number | null>(99.99);
const [smallValue, setSmallValue] = useState<number | null>(49.99);

<div className="flex flex-col gap-4">
  <CurrencyInput
    value={defaultValue}
    currency="$"
    onChange={setDefaultValue}
    size="default"
  />
  <CurrencyInput
    value={smallValue}
    currency="$"
    onChange={setSmallValue}
    size="sm"
  />
</div>

International Locales

Currency input with different locales and currencies. Each locale uses proper number formatting and currency symbol positioning.

Loading example...
import { Label } from '@wandercom/design-system-web';

const [usdValue, setUsdValue] = useState<number | null>(1234.56);
const [eurValue, setEurValue] = useState<number | null>(1234.56);
const [gbpValue, setGbpValue] = useState<number | null>(1234.56);
const [jpyValue, setJpyValue] = useState<number | null>(150000);

<div className="flex flex-col gap-4">
  <div className="flex flex-col gap-2">
    <Label htmlFor="usd-input">United States (en-US)</Label>
    <CurrencyInput
      id="usd-input"
      value={usdValue}
      currency="USD"
      locale="en-US"
      onChange={setUsdValue}
    />
  </div>
  <div className="flex flex-col gap-2">
    <Label htmlFor="eur-input">Germany (de-DE)</Label>
    <CurrencyInput
      id="eur-input"
      value={eurValue}
      currency="EUR"
      locale="de-DE"
      onChange={setEurValue}
    />
  </div>
  <div className="flex flex-col gap-2">
    <Label htmlFor="gbp-input">United Kingdom (en-GB)</Label>
    <CurrencyInput
      id="gbp-input"
      value={gbpValue}
      currency="GBP"
      locale="en-GB"
      onChange={setGbpValue}
    />
  </div>
  <div className="flex flex-col gap-2">
    <Label htmlFor="jpy-input">Japan (ja-JP)</Label>
    <CurrencyInput
      id="jpy-input"
      value={jpyValue}
      currency="JPY"
      locale="ja-JP"
      onChange={setJpyValue}
    />
  </div>
</div>

With Min and Max Validation

Currency input with minimum and maximum value constraints.

Loading example...
const [value, setValue] = useState<number | null>(50);

<div className="flex flex-col gap-2">
  <Label htmlFor="budget-input">Budget (min: $10, max: $100)</Label>
  <CurrencyInput
    id="budget-input"
    value={value}
    currency="$"
    min={10}
    max={100}
    onChange={setValue}
    placeholder="Enter amount"
  />
</div>

Disabled

Currency input in disabled state.

Loading example...
const [value, setValue] = useState<number | null>(500);

<CurrencyInput
  value={value}
  currency="$"
  onChange={setValue}
  disabled
/>

Props

value?:

number | null
The numeric value of the input. When null, displays the placeholder.

currency?:

string
Currency code (e.g., "USD", "EUR", "GBP") or symbol (e.g., "$", "€", "£"). Defaults to "$".

locale?:

string
BCP 47 locale tag for number formatting (e.g., "en-US", "de-DE", "fr-FR"). Defaults to "en-US".

onChange?:

(value: number | null) => void
Callback fired when the value changes. Receives the parsed numeric value or null.

onFocus?:

() => void
Callback fired when the input receives focus.

onBlur?:

() => void
Callback fired when the input loses focus.

placeholder?:

string
Placeholder text displayed when value is null. Defaults to "0.00".

min?:

number
Minimum allowed value. Values are clamped to this minimum on blur.

max?:

number
Maximum allowed value. Values are clamped to this maximum on blur.

size?:

'default' | 'sm'
Size variant of the input. Defaults to "default".

disabled?:

boolean
Whether the input is disabled. Defaults to false.

required?:

boolean
Whether the input is required in forms. Defaults to false.

id?:

string
Optional ID for the input element. Auto-generated if not provided.

name?:

string
Form field name for form submission.

className?:

string
Additional CSS classes to apply to the InputGroup container.

aria-label?:

string
Accessible label for the input.

aria-labelledby?:

string
ID of the element that labels the input.

aria-describedby?:

string
ID of the element that describes the input.

aria-invalid?:

boolean | 'true' | 'false'
Indicates the input has a validation error.

Behavior

Formatted Display - When not focused, displays the formatted currency value using Intl.NumberFormat with locale-specific formatting (e.g., "1,234.56").

Raw Editing - When focused, switches to raw numeric input for easy editing without formatting characters.

Automatic Parsing - On blur, parses the input value and converts it to a number, stripping non-numeric characters.

Value Clamping - If min or max props are provided, values are automatically clamped to the allowed range on blur.

Locale Support - Uses Intl.NumberFormat for locale-aware number formatting with proper thousands separators and decimal points.

Currency Symbols - Extracts currency symbols from currency codes (e.g., "USD" → "$") using Intl.NumberFormat, or uses custom symbols directly.

Keyboard Support - Press Enter to confirm and blur the input. Press Escape to cancel editing and restore the previous value.

Null Handling - When value is null or undefined, displays the placeholder text.

Accessibility

This component is built using InputGroup components and follows standard input accessibility patterns:

Keyboard Navigation:

  • Tab - Move focus to/from the input
  • Enter - Confirm value and blur input
  • Escape - Cancel editing and restore previous value

Screen Reader Support:

  • Supports aria-label, aria-labelledby, and aria-describedby for proper labeling
  • Supports aria-invalid for validation states
  • Currency symbol is visible but input receives focus for editing

Form Integration:

  • Supports name prop for native form submission
  • Supports required and disabled states
  • Compatible with form validation libraries
  • Input Group - Composable input group components
  • Number Input - Number input with increment/decrement buttons
  • Input - Basic text input component
CurrencyInput