# Currency Field
A form field for entering monetary amounts, with a currency symbol prefix
Section: components
Docs: https://nebula.kaluza.com/components/currency-field
---
## When to use
- When the user needs to enter a monetary amount, such as a payment value, budget, or balance
- When the currency context helps users understand what to enter, for example when the amount could be confused with a percentage or unit quantity
- When the accepted currencies are GBP, EUR, AUD, or USD
## When not to use
- **For plain numbers without currency context** - use [Text Field](/components/text-field) instead.
- **To display a read-only currency value** - use [Summary List](/components/summary-list) or a plain text element instead.
- **For currencies other than GBP, EUR, AUD, or USD** - the component does not support other currencies. Use [Text Field](/components/text-field) with appropriate labelling instead.
- **To indicate that a field is unavailable** - the disabled state is not supported. Use [Summary List](/components/summary-list) to show values that cannot be edited, or conditionally hide the field if it is not relevant in the current context.
## Anatomy
1. **Label**
2. **Hint**
3. **Currency symbol** (displayed as a prefix icon inside the input, not editable by the user)
4. **Input**
5. **Error message**
6. **Required/Optional indicator**
## Basic usage
```tsx
```
## Variants and features
### Currency
Use the `currency` prop to set the currency. The symbol is displayed as a prefix inside the input and announced to screen readers via a visually hidden label.
Supported values: `GBP` (default), `EUR`, `AUD`, `USD`.
```tsx
```
### Common form features
The following features are shared across all Nebula form fields. See [Form guidance](/patterns/forms) for full detail.
**Validation and errors:** when a field is invalid, an error message appears below the input and the input border changes to indicate the error state.
**Hint text:** use the `hint` prop to provide additional context. For currency fields, hint text is useful for clarifying expected format, for example "Do not include pence" or "Enter the amount in pounds".
**Required and optional:** fields are required by default. Use the `optional` prop to mark a field as optional. The required or optional state is indicated visually as part of the label.
**Field width:** use the `characters` prop to set the input width to match the expected length of the content. The default of 25 characters is appropriate for most monetary amounts. Currency Field is responsive by default and displays full width below the smallest breakpoint. Override with `fullWidth="always"` or `fullWidth="never"`.
```tsx
```
## Grouping
Currency fields are typically part of a larger form. See the [Form component](/components/form) and [form guidelines](/patterns/forms) for guidance on grouping and layout.
## Rules and guidance
### Usage
- **CurrencyField** MUST only be used when the user is entering a monetary amount. Rationale: The currency symbol prefix and decimal inputMode create an expectation of monetary input. Using it for other numeric input misleads users. [id: currencyfield.use-for-monetary-input; testability: manual]
- **CurrencyField** MUST only use one of the four supported currency values (GBP, EUR, AUD, USD). Rationale: These are the only values that map to a symbol icon and a visually hidden label. Other currencies have no symbol or accessible label. [id: currencyfield.supported-currencies-only; testability: auto]
- **CurrencyField** MUST NOT be used to display a read-only value. Rationale: It renders an interactive input element. Use Summary List or a text element for read-only currency values. [id: currencyfield.do-not-use-for-read-only; testability: manual]
- **CurrencyField** MUST NOT be passed the disabled prop. Rationale: The prop passes through to the native input with no Nebula styling and removes the field from the tab order, making it invisible to keyboard users with no accessible indication of why. Use error handling or conditional visibility instead. [id: currencyfield.no-disabled-state; testability: auto]
- **CurrencyField** SHOULD be replaced with plain text. For example in a SummaryList. When displaying a value the user cannot edit. Rationale: A field that looks interactive but cannot be changed is confusing. Plain text communicates the value without implying editability. [id: currencyfield.non-editable-as-text; testability: manual]
### Content
- **Label** MUST always be provided. Rationale: id and label are required props. The label is the primary accessible name for the input. [id: currencyfield.label-required; testability: auto]
- **Label** MUST describe the monetary value being entered, not the currency. Rationale: The currency symbol is already shown as a prefix. The label should describe the value, for example "Monthly budget" not "Amount in pounds". [id: currencyfield.label-describes-value; testability: manual]
- **Error message** MUST describe the exact problem and what the user should do to correct it. Rationale: Vague messages like "Invalid amount" do not help users understand what is wrong. Prefer "Enter an amount greater than zero" or "Enter a number, for example 250". [id: currencyfield.error-message-specific; testability: manual]
- **CurrencyField** MUST NOT require or expect the user to type the currency symbol. Rationale: The component renders the symbol as a prefix icon. Including the symbol in user input will break value parsing. [id: currencyfield.no-symbol-in-input; testability: manual]
- **Hint** SHOULD be provided when the expected format or constraints are not obvious from the label alone. Rationale: Monetary inputs often have constraints (e.g. no pence, minimum value) that users may not anticipate. [id: currencyfield.hint-for-format; testability: manual]
- **Error message** SHOULD NOT repeat the currency symbol in the error message. Rationale: The symbol is already visible in the input prefix. Repeating it adds noise without value. [id: currencyfield.error-message-no-symbol; testability: manual]
### Interaction
- **Currency symbol** WILL be announced to screen readers via a visually hidden label added by the component. Rationale: The component automatically adds a visually hidden element (e.g. "pounds") linked via aria-describedby. Do not suppress or override this. [id: currencyfield.currency-announced; testability: auto]
- **CurrencyField** MUST use inputMode="decimal" unless an integer-only value is explicitly required. Rationale: The component defaults to inputMode="decimal" to trigger the numeric keyboard with a decimal separator on mobile. Overriding to "numeric" removes the decimal point for users who need to enter pence. [id: currencyfield.decimal-input-mode; testability: auto]
- **CurrencyField** MUST validate on form submission, not while the user is typing. Rationale: Inline validation on change causes premature errors and degrades usability. See forms pattern guide. [id: currencyfield.validate-on-submit; testability: manual]
### Composition
- **CurrencyField** MUST always receive an id prop. Rationale: id is required to associate the label with the input via htmlFor and to generate the error element id for aria-describedby. [id: currencyfield.id-required; testability: auto]
- If used inside a Form component: **CurrencyField** MUST include a name prop when used inside a Form component. Rationale: Form uses the name prop to route errors from the errors array to the correct field via context. [id: currencyfield.name-when-in-form; testability: auto]
- **CurrencyField** SHOULD set the characters prop to reflect the expected length of the input value. Rationale: Input width is a affordance. An oversized input for a small value (e.g. a two-digit count) can imply a larger entry is expected. [id: currencyfield.characters-appropriate; testability: manual]
## Accessibility
### In brief
Currency Field is a native text input for an amount of money, with the currency symbol drawn inside it. The symbol is hidden from screen readers, and the currency name (pounds, euro or dollar) is added to the field's description, not to its name.
### What Nebula provides
- A real `label` linked to the input by `id`, with `aria-required` unless the field is `optional`.
- A decimal keypad on touch devices, through `inputMode`.
- An `error` sets `aria-invalid` and is linked to the input with `aria-describedby`. The message has an icon as well as colour.
- The symbol is hidden from screen readers, and the currency name is linked to the input as part of its description.
### Keyboard behaviour
The input is a native `input`, one tab stop reached with `Tab`, and it shows a focus ring when focus comes from the keyboard.
#### Interactions
- **Tab** - Moves focus to the input
### Accessible design
#### If the unit matters, say it in the label
The currency name is a description, which some screen readers skip or read late, and Australian and US dollars are both announced as "dollar". Write "Direct Debit amount in euros" when the symbol alone could be misread.
WCAG: [3.3.2 Labels or Instructions](https://www.w3.org/WAI/WCAG22/Understanding/labels-or-instructions.html)
#### Never use the placeholder as the label or the hint
A placeholder disappears as soon as the user types, and Nebula shows it at half opacity. Put the question in the label and any extra guidance, such as the decimal format, in the hint.
WCAG: [3.3.2 Labels or Instructions](https://www.w3.org/WAI/WCAG22/Understanding/labels-or-instructions.html)
#### Keep the hint short
The hint sits inside the label element, so a screen reader reads it as part of the field's name. Put what the user needs first in the label and keep the hint to one short line.
WCAG: [3.3.2 Labels or Instructions](https://www.w3.org/WAI/WCAG22/Understanding/labels-or-instructions.html)
### Accessible development
#### Always pass errors through `error`, or through a `Form` with the field `name`
This is what sets `aria-invalid` and links the message to the input. Text placed beside the field is not announced with it.
WCAG: [3.3.1 Error Identification](https://www.w3.org/WAI/WCAG22/Understanding/error-identification.html)
#### Avoid the browser's own form validation for the amount
The input carries `pattern="[0-9]*"`, which a form that is not `noValidate` rejects for a decimal such as 25.50 with a browser message. Validate the amount on submit and show the result through `error`.
WCAG: [3.3.1 Error Identification](https://www.w3.org/WAI/WCAG22/Understanding/error-identification.html)
### Accessibility testing status
Reviewed on 9 October 2026 against v17.0.1.
| Test | Status | Tested with |
| --- | --- | --- |
| Default state | Tested | axe-core via the Storybook test runner (CI) |
| Advanced states | Partially tested | axe-core via the Storybook test runner (CI) |
| Keyboard navigation | Manually tested | keyboard (reported by the owner) |
| Screen reader | Partially tested | VoiceOver with Chrome (reported by the owner) |
What each test covers:
- Default state: The component as it first appears, before anything is clicked or changed.
- Advanced states: Every other state and variant: error, open, selected, each size and style, and hovering.
- Keyboard navigation: Whether every function can be reached and used with the keyboard alone, with a visible focus ring.
- Screen reader: Whether a screen reader announces what is on screen correctly: the name, the role and any state.
What these statuses mean:
- Tested: Every check in this row passed in an automated run that repeats on every pull request.
- Manually tested: Every check in this row was run once, by a person or with a tool they started, and all of them passed. It is not re-run on every change.
- Partially tested: Some checks in this row were run once and passed; the rest are still to do.
- Issue found: A check in this row failed. The ticket is shown beside the status.
- Not tested: No check in this row has been run yet.
- Not applicable: This row does not apply to the component, for the reason given beside it.
The table records which checks were run on this component on its own. It is not a statement of WCAG conformance, and it does not cover how the component is used.
## Properties
### CurrencyField
Also accepts all props from `HTMLAttributes`, `InputHTMLAttributes`.
- `id` (type `string`, required). Unique id for the input, used to link the label, hint and error to it. Also used as the base for related ids such as `{id}-error`.
- `error` (type `ReactNode`, optional). Error message shown below the input. When set, the input gets `aria-invalid` and an error border, and the message is linked to it with `aria-describedby`. Takes precedence over an error provided by a surrounding `Form`.
- `label` (type `string`, required). Visible label for the field. It is associated with the input, so it forms the input's accessible name.
- `name` (type `string`, optional). The `name` attribute passed to the input. Also used to look up the field's error from a surrounding `Form`.
- `hint` (type `ReactNode`, optional). Helper text shown below the label. It is part of the label element, so it is read as part of the input's accessible name.
- `optional` (type `boolean`, optional, default `false`). Marks the field as not required. This appends "(optional)" to the label (or, when the theme marks required fields, hides the required asterisk) and sets `aria-required` on the input only when the field is not optional.
- `fullWidth` (type `'always' | 'never' | 'small'`, optional, default `'small'`). Controls when the field stretches to the full width of its container. `always` is always full width, `never` always uses the width set by `characters`, and `small` is full width on small screens only.
- `characters` (type `number`, optional, default `25`). Width of the input, as an approximate number of characters (the width is set to `0.8em` per character, rounded up). Does not limit how much can be typed.
- `affixWidth` (type `number`, optional, default `40`). Extra width in pixels added to the width calculated from `characters`, to make room for icons or buttons inside the input.
- `currency` (type `'GBP' | 'EUR' | 'AUD' | 'USD'`, optional, default `'GBP'`). Currency whose symbol is shown in the input. `GBP` shows a pound sign, `EUR` a euro sign, and `AUD` and `USD` both a dollar sign. The currency name is also announced to screen readers.
- `iconAlignment` (type `'left' | 'right'`, optional, default `'left'`). Which side of the input the currency symbol appears on.