Money input

A currency field with live grouping, stable width, rolling digits, and minor-unit output.

inputsnew
Preview
Money input preview

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

Customer pays 1250.00.

Keyboard interactions

KeysAction
ArrowUp / ArrowDownAdds or subtracts one step (one major unit by default).
Shift+ArrowUp / Shift+ArrowDownMoves ten steps.
PageUp / PageDownMoves ten steps.
EnterSettles the fraction, such as 12.5 to 12.50.
Backspace / DeleteOver 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.