Use a memorable, unique name.
Default — kr
Overview
MoneyField is a thin wrapper around TextFieldthat bakes in the rules every monetary input across Flatpay needs to honour: European number formatting, tabular numerals, slashed zero, and a currency suffix that always sits to the right of the value. It's the only field that ships with a hero-size variant — for screens where the amount is the answer the page is asking for.
Negatives are orange, not red
When you display a value (not edit one), negatives use text.numeric.negative (orange.700), not danger red. Outgoing money is normal product motion; red is reserved for errors. See Currency typesetting for the full rules.
Currencies
Pass the currency code via the currency prop. The suffix is always set in mono, ALL CAPS, tracked — visually distinct from the value.
Use a memorable, unique name.
Default — Danish kroner
Currency code as a mono suffix. "kr" for the home market.
EUR-priced surfaces use the symbol, not the ISO code.
Euro
Same component, different suffix. Pass currency="€".
Pound sterling
GBP for the UK market.
ISO code
Pass the three-letter code when several currencies share a symbol or the locale needs disambiguation.
States
MoneyField inherits every state from TextField — the same border tones, focus ring, error treatment.
Empty
Resting state. Label visible, value blank.
Use a memorable, unique name.
Filled
The user has typed a value.
Set during onboarding.
Disabled
Inert — set during onboarding, locked here.
Validation failed.
Invalid
Pink container, red border, error message replaces help text.
Hero size
For screens where the amount is the question — funding requests, balance edits, single-question forms — set size="hero". The value renders at metric.lg with tabular numerals, the suffix shifts to align with the new baseline, and the surface gets more breathing room.
size="hero"
Used when the amount is the page's headline — funding screens, balance overrides, single-question forms. Renders the value at metric.lg with tabular-nums + slashed zero.
One hero per page
The hero size is meant for a single signature surface. Two hero inputs on the same page cancel each other; reach for the default size for everything else.
Anatomy
Five named parts — the same as TextField, with the suffix slot reserved for the currency code.
Use a memorable, unique name.
Container
8 px radius. Same border / hover / focus / error tones as TextField.
Label
Always-on floating label at 60% foreground.
Value
Tabular numerals, slashed zero. European format — period separates thousands, comma separates decimals.
Currency suffix
Mono, ALL CAPS, tracked. Set via the
currencyprop — defaults tokrfor the home market.Help / error
Same affordance as every Form field — help in 60% foreground, error in #8E191B with an alert glyph.
Behavior
- Decimal keyboard on mobile.
inputMode="decimal"by default — iOS and Android raise the numeric pad with comma and decimal point, never the full QWERTY. - European format isn't parsed by <input type="number">. MoneyField uses
type="text"under the hood so the comma decimal stays valid. Validate the value yourself on submit, or useIntl.NumberFormat+ a controlled-component pattern. - Suffix is presentational. The
currencyprop never enters the form value — only the numeric input does. Submit the currency separately as a hidden field if your backend needs it. - Hero size is a layout decision. Use it when the amount headlines the screen. Avoid it on dense forms — three hero inputs in a column is the visual equivalent of three primary buttons.
Accessibility
- Currency in the accessible name. Include the currency in the label (e.g.
label="Amount (DKK)") for fields where the suffix alone could be ambiguous to a screen-reader user. The visual suffix isaria-hiddenby default. - Decimal precision is server-side.Don't rely on the field to enforce two-decimal places — let the backend normalise. The component just keeps the value as-typed.
- Error semantics match every other Form field:
aria-invalid+ error message linked viaaria-describedby.
Code
tsx
import { MoneyField } from "@flatpay-dk/ui";
// Default — Danish kroner
<MoneyField
label="Amount"
defaultValue="10,00"
description="Use a memorable, unique name."
/>
// Different currency
<MoneyField label="Amount" currency="€" defaultValue="10,00" />
// ISO code when the locale calls for it
<MoneyField label="Amount" currency="DKK" defaultValue="10,00" />
// Hero — funding screen, single-question forms
<MoneyField
size="hero"
label="Funding amount"
defaultValue="500000"
currency="kr"
/>
// Invalid + error
<MoneyField
label="Amount"
defaultValue="10,00"
error="Must be at least 100,00 kr."
/>Best practices
Do
Use the hero size when the amount is the page's headline.
Don't
Don't stack two hero amounts on the same page — the eye loses the headline.
Do
Pass the currency as a prop. The suffix is presentational, not part of the value.
Don't
Don't bake the symbol into the value. Forms posting to a backend need a clean number.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| currency | string | "kr" | Currency code rendered as the suffix. Pass the symbol (€, £) for symbol-currencies; the ISO code (DKK, EUR) when the surface needs disambiguation. |
| label | ReactNode | — | Floating label inside the container. Include the currency when assistive tech might not infer it from the suffix. |
| description | ReactNode | — | Help text below the container. |
| error | ReactNode | — | Error message; flips the surface to error tones and sets aria-invalid. |
| size | "md" | "hero" | "md" | Vertical density and value typography. Hero is reserved for headline-amount surfaces; use sparingly. |
| ...rest | Omit<TextFieldProps, 'type' | 'suffix'> | — | All other TextField props pass through (name, required, min, max, prefix, etc.). |