Money input
A currency field with live grouping, stable width, rolling digits, and minor-unit output.
inputsnew
Preview
Money input preview
Live · keyboard ready
1250.00 in the currency above
When to use
- Payment, tip, transfer, or budget amounts entered by hand.
- Invoice and expense forms where the currency may change.
- Amount fields that benefit from quick add chips such as +$10.
When not to use
- Use number-field for quantities and non currency numbers.
- Use slider or price-ladder when people pick from a range rather than type.
- Use scrub-input for design tool style drag adjustments.
shadcn CLI
Also installs (1)
- manicat-foundation
Manual install(+)
registry.jsonjson
{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "money-input",
"type": "registry:ui",
"title": "Money input",
"description": "A currency field with live grouping, stable width, rolling digits, and minor-unit output.",
"categories": [
"inputs"
],
"dependencies": [
"lucide-react",
"motion"
],
"registryDependencies": [
"https://manicat.viraza.net/r/manicat-foundation.json"
],
"files": [
{
"path": "registry/components/money-input/money-input.tsx",
"type": "registry:component",
"target": "@components/manicat/money-input/money-input.tsx"
},
{
"path": "registry/components/money-input/money-input.module.css",
"type": "registry:component",
"target": "@components/manicat/money-input/money-input.module.css"
}
],
"docs": "Docs and live preview: https://uiarc.dev/components/money-input",
"meta": {
"tier": "free",
"kind": "component",
"docs": "https://manicat.viraza.net/components/money-input",
"markdown": "https://uiarc.dev/components/money-input/markdown",
"tags": [
"inputs",
"new"
]
}
}Usage
Install the component, then copy this into your app. Change the content and the props to fit your screen.
example.tsxtsx
"use client";
import { useState } from "react";
import { MoneyInput } from "@/components/manicat/money-input/money-input";
export function MoneyInputExample() {
const [amount, setAmount] = useState<number | null>(1250);
return (
<div className="grid w-full max-w-sm gap-3">
<MoneyInput label="Amount" value={amount} onValueChange={setAmount} defaultCurrency="USD" />
<p className="text-sm text-(color:--text-muted)">{amount === null ? "Nothing charged." : `${amount.toFixed(2)} in the currency above`}</p>
</div>
);
}
Examples
Real screens rather than isolated props. Switch to Code to copy a whole example into your project.
Invoice amount A currency field that returns minor-unit-safe numbers.
Invoice amount
A currency field that returns minor-unit-safe numbers. preview
Live · keyboard ready
Customer pays 1250.00.
invoice-amount-a-currency-field-that-returns-minor-unit-safe-numbers-.tsxtsx
"use client";
import { useState } from "react";
import { MoneyInput } from "@/components/manicat/money-input/money-input";
export function MoneyInputInvoiceExample() {
const [amount, setAmount] = useState<number | null>(1250);
return (
<div className="grid w-full max-w-sm gap-3">
<MoneyInput label="Invoice amount" value={amount} onValueChange={setAmount} defaultCurrency="USD" />
<p className="text-sm text-(color:--text-muted)">{amount === null ? "Nothing will be charged." : `Customer pays ${amount.toFixed(2)}.`}</p>
</div>
);
}
Keyboard interactions
| Keys | Action |
|---|---|
| ArrowUp / ArrowDown | Adds or subtracts one step (one major unit by default). |
| Shift+ArrowUp / Shift+ArrowDown | Moves ten steps. |
| PageUp / PageDown | Moves ten steps. |
| Enter | Settles the fraction, such as 12.5 to 12.50. |
| Backspace / Delete | Over a group separator, deletes the digit beside it. |
| ArrowDown / ArrowUp (on the currency button) | Opens the currency menu. |
| ArrowDown / ArrowUp / Home / End (in the menu) | Moves through currencies; typing jumps by code or name. |
| Enter / Space (in the menu) | Picks the highlighted currency. |
| Escape (in the menu) | Closes the menu and returns to the button. |
Accessibility
- The input has role="spinbutton" with aria-valuenow in major units, aria-valuetext as the full currency string, and min and max when set.
- The animated digits are aria-hidden; the real input carries the value, so screen readers and autofill see plain text.
- The range, hint, and error are linked with aria-describedby; limits, chip adds, and currency changes are announced politely.
- The currency menu is a listbox with aria-activedescendant and typeahead.
Motion
- Chips and arrow keys roll each digit column in the direction of change while new columns open their width; typing cuts straight to the result so the caret never lags.
- A refused change kicks the amount sideways on a bouncy spring and flashes the range.
- The currency symbol swaps with a short roll while its slot springs to the new width; long amounts scale down to fit on a spring.
- Reduced motion removes the rolls, kick, and width springs; digits fade and sizes jump.
Responsive behavior
- Under 420px viewport width the amount text drops a size and the padding tightens.
- A ResizeObserver scales the amount down to fit its box instead of scrolling, so long numbers stay readable on phones.
- inputMode is decimal (numeric for zero decimal currencies like JPY), which brings up the number keypad on touch.
Performance
- Intl.NumberFormat instances are cached per locale and currency.
- Each digit is its own animated column; amounts are capped at 12 integer digits, which keeps the column count small.
Notes for AI
- Store the value in minor units (integers) to avoid floating point errors; details.major and details.formatted are for display.
- Use for money only. For plain numbers with units use number-field, and for drag-to-adjust values use scrub-input.
- Pass one code in currencies to lock the currency; the code then shows as static text.
- max refuses typing past it; min is only checked on blur, so people can type through smaller amounts on the way.