Number Input
Enter an amount — raw or format-as-you-type, with optional currency, Max, and available balance.
Unformatted string — Houston day counts, app-owned parsing.
- value
- —
Thousands separators and decimal limits as you type.
- formattedValue
- —
- value
- —
- floatValue
- —
You might be looking for:
- Input
Single-line text entry.
- Textarea
Multi-line text entry.
- Date Picker
Pick a calendar date from a popover.
Modes
NumberInput has two modes, controlled by whether you pass format:
| Raw (default) | Formatted (format set) | |
|---|---|---|
| Control | value + onChange | value + onValueChange |
| What the user types | Stored as-is | Masked with separators / decimal limits |
| Typical use | Houston admin, app-owned parsing | Portal trade compose |
Formatted values
When format is set, onValueChange receives three fields:
| Field | Example | Use for |
|---|---|---|
formattedValue | "12,344,444.00" | What the user sees in the field |
value | "12344444.00" | Form state / API payload (no separators) |
floatValue | 12344444 | Quote math (Number.parseFloat without parsing yourself) |
Store value in React state or React Hook Form and pass it back with
valueIsNumericString. Use floatValue when you need a number for
calculations. formattedValue is mostly for display or debugging — the
component applies it to the input for you.
Examples
Cash amount
Currency prefix, clear affordance, and Max chip — with formatting enabled.
€4,540 available
- formattedValue
- —
- value
- —
- floatValue
- —
Currency row
The designed second row: the dropdown chip picks the currency and the availability sits opposite.
£765.00 available
Limit price
Stack two amount rows in one field with a divider between the sections.
€4,540 available
Quantity
Units and share counts — up to four decimal places, no currency prefix.
Up to four decimal places.
Validation error
The error ring and halo come from the wrapping Field. The footer pairs
the message with a recovery action.
React Hook Form
Store the unformatted numeric string (values.value) in form state and
pass valueIsNumericString so separators are not double-applied.
Playground
<Field> <NumberInput prefix="€" format="currency" placeholder="0.00" value={value} onValueChange={…} valueIsNumericString onClear={…}> <InputGroupButton variant="chip" size="xs">Max</InputGroupButton> </NumberInput></Field>Usage
import { InputGroupButton } from "@sophic/portal-ui/components/ui/input-group";
import { NumberInput } from "@sophic/portal-ui/components/ui/number-input";
import type { NumberInputChangeValues } from "@sophic/portal-ui/components/ui/number-input";Raw entry (Houston, app-owned parsing):
<Field>
<NumberInput
prefix="€"
placeholder="0.00"
value={value}
onChange={(e) => setValue(e.target.value)}
onClear={() => setValue("")}
/>
</Field>Format-as-you-type (portal trade compose):
const [values, setValues] = useState<NumberInputChangeValues>({
value: "",
formattedValue: "",
floatValue: undefined,
});
<Field>
<NumberInput
format="currency"
prefix="€"
value={values.value}
valueIsNumericString
onValueChange={setValues}
onClear={() => setValues({ value: "", formattedValue: "", floatValue: undefined })}
>
<InputGroupButton variant="chip" size="xs">Max</InputGroupButton>
</NumberInput>
</Field>NumberInput is the amount row only — it always lives inside a Field, which carries the card, focus ring, and error state. Trailing adornments are children.
variant="tall" is one --button-height-lg row, the same height as Input
variant="tall" and Button size="lg". Drop the
Field's vertical padding when you use it, so the card does not grow past that.
For a convenience wrapper, see useNumberInput in
@sophic/portal-ui/lib/use-number-input.
Props
Raw mode
Omit format. Also accepts every native <input> attribute (value,
onChange, placeholder, disabled, …).
Prop
Type
Default
Formatted mode
Set format to currency, quantity, or integer. Use onValueChange
instead of onChange. Also accepts native <input> attributes except
type and onChange.
Prop
Type
Default
onValueChange payload
Prop
Type
Default
Changelog
- 20 August 2026 — “You might be looking for” also maps to Date Picker. Full notes
- 19 August 2026 — “You might be looking for” map to Input and Textarea. Full notes
- 4 August 2026 — Optional
formatprop merges format-as-you-type masking into Number Input (currency,quantity,integerpresets). Full notes - 22 July 2026 — Max chip matches the currency picker pill. Full notes
- 22 July 2026 — Invalid state colors the message only (amount stays normal). Full notes
- 15 July 2026 — Currency chip is
SelectTrigger variant="chip". Full notes - 15 July 2026 — Added (display-type amount row with clear affordance). Full notes