Skip to content

Inputs

Number, currency and percentage

One numeric control, three unit contracts. The value the component reports is always a plain number in the canonical unit — formatting is presentation, and it never rewrites what you stored.

Examples

Number

Locale-aware parsing and formatting, caret-safe editing, arrow-key steps and real spinbutton semantics come from React Aria. LUC owns the shell and the steppers.

Integer, stepped by one.

Two decimals, no step grid — 1234.567 stays 1234.567.

Milliseconds; the unit is a suffix, not part of the value.

Currency

The currency is a format, not a policy: the value is the major unit as a number (250000 renders $250,000.00), and any ISO-4217 code works.

USD

EUR, de-DE grouping and separators.

JPY has no minor unit; precision is configuration.

canonical value: 250000 · displayed as $250,000.00

Percentage

`scale` is required, because 0.12 is ambiguous. The component converts only between the canonical value and the display value — it never guesses which one you meant.

scale="fraction" — the stored value is 0.045, displayed as 4.5%.

scale="percent" — the stored value is 8, displayed as 8.0%.

canonical value: 0.045 (a fraction, not percent points)

States

The same six states as every other LUC control: hover, focus, invalid, disabled and read-only share one shell.

Default

Invalid

commitBehavior="validate" reports the range instead of snapping to it

Disabled

Read-only

No steppers

compact filter bars

Small

Usage

import { NumberInput, CurrencyInput, PercentageInput } from "@luc/ui/forms";
<NumberInput value={lookback} onValueChange={setLookback} min={5} max={400} step={5} aria-label="Lookback (days)" />

`value` plus `onValueChange` controls it; a number commits on blur or Enter, and `step` is what the steppers move by.

API

Read from the published TypeScript API, so a prop that is not listed here does not exist and a default that is listed is the one the compiler sees.

Exported symbols
ExportKindSignature
CurrencyInput27 documented props({ currency, decimals, ...props }: CurrencyInputProps) => import("@types/react").JSX.Element
NumberInput28 documented props({ value, defaultValue, onValueChange, onBlur, min, max, step, formatOptions, decimals, locale, size, unit, leadingIcon, steppers, invalid, disabled, readOnly, required, placeholder, name, id, isWhee…
PercentageInput27 documented props({ scale, step, decimals, value, defaultValue, onValueChange, ...props }: PercentageInputProps) => import("@types/react").JSX.Element

NumberInput props

Type: NumberInputProps

Values the compiler reads from the declaration.

NumberInput props
PropTypeDefaultNotes
aria-label?string | undefined—
className?string | undefined—
commitBehavior?"snap" | "validate" | undefined—`snap` clamps to min/max/step on blur, which is React Aria's default.
decimals?number | undefined—Convenience for `minimumFractionDigits` + `maximumFractionDigits`.
decrementAriaLabel?string | undefined—
defaultValue?number | null | undefined—
disabled?boolean | undefined—
formatOptions?Intl.NumberFormatOptions | undefined—`Intl.NumberFormatOptions`. Use `CurrencyInput`/`PercentageInput` presets.
id?string | undefined—
incrementAriaLabel?string | undefined—
inputClassName?string | undefined—
invalid?boolean | undefined—
isWheelDisabled?boolean | undefined—Scroll-wheel editing is off by default; a page should not edit itself.
leadingIcon?ReactNode—
locale?string | undefined—BCP-47 locale. Inherits the surrounding locale when omitted.
max?number | undefined—
min?number | undefined—
name?string | undefined—
onBlur?(() => void) | undefined—Called on blur; convenient for engines that validate on touch.
onValueChange?((value: number | null) => void) | undefined—
placeholder?string | undefined—
readOnly?boolean | undefined—
required?boolean | undefined—
size?ControlSize | undefined—
step?number | undefined—Step grid. Omit it and the field keeps the value you gave it — the steppers still move by React Aria's default (1, or 0.01 for a percentage). Set a step only when the domain reall…
steppers?boolean | undefined—Chevron steppers. Keyboard and screen readers work either way.
unit?ReactNode—Text or glyph shown after the value. Presentation only, never in the value.
value?number | null | undefined—
CurrencyInput props27 props

CurrencyInput props

Type: CurrencyInputProps

Values the compiler reads from the declaration.

CurrencyInput props
PropTypeDefaultNotes
aria-label?string | undefined—
className?string | undefined—
commitBehavior?"snap" | "validate" | undefined—`snap` clamps to min/max/step on blur, which is React Aria's default.
currencystring—ISO-4217 code. The component makes no assumption about which one.
decimals?number | undefined—Fraction digits. Defaults to 2 — a currency field that hides cents is a bug.
decrementAriaLabel?string | undefined—
defaultValue?number | null | undefined—
disabled?boolean | undefined—
id?string | undefined—
incrementAriaLabel?string | undefined—
inputClassName?string | undefined—
invalid?boolean | undefined—
isWheelDisabled?boolean | undefined—Scroll-wheel editing is off by default; a page should not edit itself.
leadingIcon?ReactNode—
locale?string | undefined—BCP-47 locale. Inherits the surrounding locale when omitted.
max?number | undefined—
min?number | undefined—
name?string | undefined—
onBlur?(() => void) | undefined—Called on blur; convenient for engines that validate on touch.
onValueChange?((value: number | null) => void) | undefined—
placeholder?string | undefined—
readOnly?boolean | undefined—
required?boolean | undefined—
size?ControlSize | undefined—
step?number | undefined—Step grid. Omit it and the field keeps the value you gave it — the steppers still move by React Aria's default (1, or 0.01 for a percentage). Set a step only when the domain reall…
steppers?boolean | undefined—Chevron steppers. Keyboard and screen readers work either way.
value?number | null | undefined—
PercentageInput props27 props

PercentageInput props

Type: PercentageInputProps

Values the compiler reads from the declaration.

PercentageInput props
PropTypeDefaultNotes
aria-label?string | undefined—
className?string | undefined—
commitBehavior?"snap" | "validate" | undefined—`snap` clamps to min/max/step on blur, which is React Aria's default.
decimals?number | undefined—Convenience for `minimumFractionDigits` + `maximumFractionDigits`.
decrementAriaLabel?string | undefined—
defaultValue?number | null | undefined—
disabled?boolean | undefined—
id?string | undefined—
incrementAriaLabel?string | undefined—
inputClassName?string | undefined—
invalid?boolean | undefined—
isWheelDisabled?boolean | undefined—Scroll-wheel editing is off by default; a page should not edit itself.
leadingIcon?ReactNode—
locale?string | undefined—BCP-47 locale. Inherits the surrounding locale when omitted.
max?number | undefined—
min?number | undefined—
name?string | undefined—
onBlur?(() => void) | undefined—Called on blur; convenient for engines that validate on touch.
onValueChange?((value: number | null) => void) | undefined—
placeholder?string | undefined—
readOnly?boolean | undefined—
required?boolean | undefined—
scale"percent" | "fraction"—The unit contract, stated on purpose and never defaulted: - `fraction`: `0.12` is 12% (what `Intl` and most APIs use). - `percent`: `12` is 12% (what most people type and most for…
size?ControlSize | undefined—
step?number | undefined—
steppers?boolean | undefined—Chevron steppers. Keyboard and screen readers work either way.
value?number | null | undefined—

Guidelines

Do

  • Use `NumberInput` for a measured quantity, `CurrencyInput` for money and `PercentageInput` for a ratio — the unit is part of the component, not a suffix someone typed.
  • Give `step` the granularity the value really has: it snaps typed input onto the grid and it is what the steppers move by.
  • Set `min` and `max` when the quantity has a floor or a ceiling the form has to enforce; the steppers stop at the ends.

Don't

  • Use a text input with `parseFloat` in the handler: the component owns parsing, the decimal separator and the commit-on-blur rule.
  • Leave `scale` off `PercentageInput`: a percentage without a declared scale is the ambiguous 0.12 the component exists to prevent.
  • Format money with a currency symbol you typed — `currency` is the prop, and it drives the prefix and the grouping.

Accessibility

The control is a real `<input>` with `inputMode` set for the numeric keypad, and the steppers are buttons with names rather than decorative glyphs. A number commits on blur or Enter, so a partially typed value is never read as a different one, and `Field` supplies the label, the description and the message.

Luc UI v1.0NumberInputdark theme