# AmountInput

> The hardest input in fintech.

Source: https://lopifi.com/components/amount-input
Part of: Lopifi — US GAAP books on autopilot, reviewed by a CPA (https://lopifi.com)

---

- **Family:** Payments — 
- **Status:** Implemented
- **Spec:** https://lopifi.com/docs/catalog/03-payments

## Install

```bash
npx shadcn@latest add @lopifi/theme
npx shadcn@latest add @lopifi/amount-input
```

## Specification

> The hardest input in fintech. Get this wrong and nothing else matters.

```tsx
<AmountInput
  currency="USD"
  value={amount}
  onChange={setAmount}
  max={availableBalance}
  onMaxClick={() => setAmount(availableBalance)}
/>
```

### The rules

- **`type="text"` + `inputMode="decimal"`.** Never `type="number"` — it has scroll-wheel increment (which *will* silently change someone's payment amount), inconsistent decimal parsing across locales, and unstyleable spinners.
- **16px minimum font size** or iOS Safari zooms the viewport on focus and never zooms back.
- **Parse permissively, format on blur.** Accept `1,234.56`, `1234,56`, `1 234.56`, `$1234.56`, `1.2k`. Reject nothing a human would type. Format canonically when they leave the field.
- **Never reformat mid-typing.** Inserting a thousands separator while someone is typing moves the caret and is the single most infuriating input bug in fintech. Format on blur only.
- **Caret preservation** if you must transform during input — measure the caret offset from the *right*, not the left.
- **Never block paste.**
- **Locale-aware decimal separator.** A German user types `1234,56`. Accept it.
- **Currency-aware precision.** JPY rejects decimals entirely; KWD accepts three.
- **Max is soft by default.** Show "Exceeds available balance of $4,120.00" as an inline error, but let them type it — an overdraft may be legitimate, and blocking input to prevent an invalid state prevents valid ones too.
- **Big.** `figure-lg` (28/32) or larger. This is the most important number on the screen and it should look like it.
- **The symbol is part of the field**, at `--text-tertiary`, not a floating adornment that misaligns as the value grows.

### Accessibility
`aria-label="Amount in US dollars"` · parsed value announced on blur (`"one thousand two hundred thirty-four dollars and fifty-six cents"`) via `aria-describedby` · errors specific and actionable · `aria-invalid` on error.

### Motion
None on the value. Error message: 140ms opacity + 4px translate. Shake on invalid submit is **forbidden** — it's a stress signal in a context where the user is already anxious about money.

## Rules this component obeys

Money is minor units, never floats. `null` is not `0` and neither is redacted. Negatives render in accounting parentheses with the parenthesis column reserved on both sides. Debit and credit are two columns. Per-currency decimal places. Polarity is explicit — up is not always good. Every figure states its period, entity, currency, basis and as-of.
