Currency Input
Decimal money field with optional symbol, formatted read-only display, and pure parse/format helpers from the currency-format registry item.
Formatting lives in currency-format (no UI). currency-input is the field component and declares registryDependencies including input and the currency-format registry item (via URL in the manifest) so the CLI installs helpers when you add the input.
Currency Format
Pure parseMoneyNumber and formatMoneyForDisplay — no npm dependencies. Use in tables, summaries, or custom read-only UI.
Installation
pnpm dlx shadcn@latest add https://ui.isaacfei.com/r/currency-format.jsonnpx shadcn@latest add https://ui.isaacfei.com/r/currency-format.jsonyarn dlx shadcn@latest add https://ui.isaacfei.com/r/currency-format.jsonbun x shadcn@latest add https://ui.isaacfei.com/r/currency-format.jsonWith a namespace: npx shadcn@latest add @f-ui/currency-format.
Usage
import {
formatMoneyForDisplay,
parseMoneyNumber,
} from '@/components/f-ui/currency-format/currency-format';Example
Currency style (ISO code)
$1,234.56
Decimal style (no code)
42.50
Empty parse — helpers return null / display "-"; in tables you often pair with EmptyValuePlaceholder instead
parseMoneyNumber(""):null
Placeholder:—
"use client";
import {
formatMoneyForDisplay,
parseMoneyNumber,
} from "@/components/f-ui/currency-format/currency-format";
import { EmptyValuePlaceholder } from "@/components/f-ui/empty-value-placeholder";
export function CurrencyFormatDemo() {
const usd = formatMoneyForDisplay(1234.56, "USD", "en-US");
const plain = formatMoneyForDisplay(42.5, undefined, "en-US");
const emptyParse = parseMoneyNumber("");
return (
<div className="max-w-md space-y-4 text-sm">
<div className="space-y-1">
<p className="text-muted-foreground text-xs">Currency style (ISO code)</p>
<p className="font-mono tabular-nums">{usd}</p>
</div>
<div className="space-y-1">
<p className="text-muted-foreground text-xs">Decimal style (no code)</p>
<p className="font-mono tabular-nums">{plain}</p>
</div>
<div className="space-y-1">
<p className="text-muted-foreground text-xs">
Empty parse — helpers return null / display "-"; in tables you often pair
with <code className="text-foreground">EmptyValuePlaceholder</code> instead
</p>
<p className="flex flex-wrap items-center gap-2">
<span className="text-muted-foreground">parseMoneyNumber(""):</span>
<span className="font-mono">{emptyParse === null ? "null" : String(emptyParse)}</span>
</p>
<p className="flex flex-wrap items-center gap-2">
<span className="text-muted-foreground">Placeholder:</span>
<EmptyValuePlaceholder />
</p>
</div>
</div>
);
}API Reference
parseMoneyNumber(value): returns a finitenumberornull; accepts numbers, bigints, or strings with grouping / locale-style separators.formatMoneyForDisplay(value, currencyCode?, locale?): formatted string, or"-"when the value cannot be parsed. With a currency code, usesIntl.NumberFormatcurrency style (two decimals). Without a code, two decimal places, no grouping, no currency symbol.
Currency Input
Controlled decimal field (inputMode="decimal") with optional currency symbol prefix and a read-only branch that formats via formatMoneyForDisplay unless you pass formatReadOnly.
Installation
pnpm dlx shadcn@latest add https://ui.isaacfei.com/r/currency-input.jsonnpx shadcn@latest add https://ui.isaacfei.com/r/currency-input.jsonyarn dlx shadcn@latest add https://ui.isaacfei.com/r/currency-input.jsonbun x shadcn@latest add https://ui.isaacfei.com/r/currency-input.jsonNamespace: npx shadcn@latest add @f-ui/currency-input — pulls currency-format via registryDependencies when using the registry manifest.
Usage
import { CurrencyInput } from '@/components/f-ui/currency-input/currency-input';
<CurrencyInput
currencySymbol="USD"
value={amount}
onChange={(e) => setAmount(e.target.value)}
/>
<CurrencyInput readOnly value={amount} currencySymbol="USD" />When readOnly is set, the default display uses formatMoneyForDisplay (treat currencySymbol as an ISO code when appropriate). Pass formatReadOnly to replace that branch entirely.
Example
Editable
Current value:1234.56
Read-only display
"use client";
import { useState } from "react";
import { CurrencyInput } from "@/components/f-ui/currency-input/currency-input";
import {
EmptyValuePlaceholder,
isEmptyDisplayValue,
} from "@/components/f-ui/empty-value-placeholder";
export function CurrencyInputDemo() {
const [amount, setAmount] = useState("1234.56");
return (
<div className="max-w-sm space-y-3">
<div className="space-y-1">
<p className="text-muted-foreground text-xs">Editable</p>
<CurrencyInput
currencySymbol="USD"
value={amount}
onChange={(e) => setAmount(e.target.value)}
aria-label="Amount"
/>
</div>
<p className="text-muted-foreground flex flex-wrap items-center gap-x-1 gap-y-0.5 text-xs">
<span>Current value:</span>
{isEmptyDisplayValue(amount) ? (
<EmptyValuePlaceholder />
) : (
<span className="text-foreground font-medium tabular-nums">{amount}</span>
)}
</p>
<div className="space-y-1">
<p className="text-muted-foreground text-xs">Read-only display</p>
<CurrencyInput readOnly value={amount} currencySymbol="USD" />
</div>
</div>
);
}API Reference
currencySymbol: optional string — prefix in editable mode; passed into default read-only formatting.readOnly: bordered display row instead of<input>; empty values show"-"from the formatter unlessformatReadOnlyhandles them.formatReadOnly:(value: string | number | undefined) => string— overrides defaultformatMoneyForDisplayfor read-only display.containerClassName: class on the outer wrapper (both modes).- Other
Input-like props apply in editable mode;typeis text withinputMode="decimal".