Components · Forms

Money field

A currency-suffixed numeric input. European format (1.234,56), tabular numerals, slashed zero. Two sizes — default for forms, hero for the moments where the amount is the headline.

Documentedby Derek Fidler

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.

  1. Container

    8 px radius. Same border / hover / focus / error tones as TextField.

  2. Label

    Always-on floating label at 60% foreground.

  3. Value

    Tabular numerals, slashed zero. European format — period separates thousands, comma separates decimals.

  4. Currency suffix

    Mono, ALL CAPS, tracked. Set via the currency prop — defaults to kr for the home market.

  5. 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 use Intl.NumberFormat + a controlled-component pattern.
  • Suffix is presentational. The currency prop 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 is aria-hidden by 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 via aria-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

PropTypeDefaultDescription
currencystring"kr"Currency code rendered as the suffix. Pass the symbol (€, £) for symbol-currencies; the ISO code (DKK, EUR) when the surface needs disambiguation.
labelReactNodeFloating label inside the container. Include the currency when assistive tech might not infer it from the suffix.
descriptionReactNodeHelp text below the container.
errorReactNodeError 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.
...restOmit<TextFieldProps, 'type' | 'suffix'>All other TextField props pass through (name, required, min, max, prefix, etc.).