When to reach for InputNumber, what each of its axes is for, and the decisions it leaves to you.
When to use
InputNumber is for a precise quantity the reader types, like a price, a count, a percentage or a duration. It knows the value is a number, so it can hold a range, a precision, a display format and buttons for small adjustments.
The reader knows the number, or can work it out, and typing it is faster than aiming at it.
Being off by one matters, as with a quantity or a price.
The value has a unit that belongs beside it, like %, kg or USD.
There is a valid range, and you want the field to keep the value inside it.
What to use instead
Slider when the reader picks a rough position in a range, like opacity, volume or a price ceiling on a filter. Dragging is faster than typing there.
Input when the digits are a code, like a phone number, a postcode, a card number or an order ID. Stepping one is meaningless, leading zeros matter, and a thousands separator is wrong.
Select when the values are few and named, like 1 to 10 guests, a rating or a number of columns. Picking one of four options is faster than typing a digit, and it cannot be out of range.
Slider with an InputNumber beside it when both apply: the slider for the sweep, the field for the exact value. They share one piece of state.
The range, and what happens outside it
min, max and step are three separate decisions, and only min and max are about validity. step defaults to 1 and sets how far the buttons and arrow keys move the value. It does not limit what can be typed, so a step of 5 still lets anyone type 7.
Set step to the smallest change worth making, usually the unit the reader thinks in: 1 guest, 0.5 hours, 5 minutes, 0.01 of a currency.
Stepping stops at the bounds. Once the value equals max, the up button is disabled, so the buttons never carry the value out of range.
Typing can. While the field has focus, a value outside the range is allowed, so someone can type 100 into a field whose min is 50. The text shows in the error color until it is back in range.
On blur or Enter, the value is pulled back into range without a message: 500 in a field with a max of 100 becomes 100, and onChange fires with 100. If the limit matters, say so in help text under the field.
changeOnBlur defaults to true, and it is what commits and clamps the value on blur. With false the reader must press Enter or use a button to commit, which suits a spreadsheet-like grid.
min and max do not clamp a value you set. A value set from code can fall outside them, so derive status from it.
onChange can pass null. Clearing the field reports null, so give your state a fallback, like setQty(value ?? 1).
Precision and floating point
precision is the number of decimal places the field shows and commits. It applies when the value is committed, not while the reader types, so typing 1.239 into a field with precision={2} shows all four digits until blur and then settles at 1.24.
Set it wherever the value has a natural scale. Money is 2, a percentage is usually 0, a rate might be 4. Without it, whatever the reader typed is kept.
The field steps in decimal arithmetic. Stepping 0.1 from 0.2 gives 0.3, with no floating-point error.
onChange hands you a number by default, so the error comes back as soon as your own code adds two of them. Keep money in minor units, or use stringMode.
decimalSeparator changes the separator in the display only, and defaults to .. It does not read a comma back, which is what parser is for. Set both together or neither.
Formatter and parser
formatter turns the value into what the field shows, and parser turns what the reader typed back into a value. Use them as a pair. A formatter that adds something the parser does not remove leaves a field that grows a currency symbol on every blur.
Use them for the unit and the grouping: $ 1,000, 80 %, 1 234 km. The parser strips what the formatter added.
The formatter receives an info object with userTyping. Separators inserted on every keystroke fight the caret, so format fully when userTyping is false, and lightly or not at all while it is true.
A percentage stored as 0.8 and shown as 80% is a conversion. Do it in your own state rather than in formatter. min, max and step apply to the value the field holds, so a formatter that multiplies by 100 gives the range a meaning the reader cannot see.
Where the formatter only decorates, use prefix for a currency symbol and suffix for a unit. They sit beside the text, so they never have to be parsed back off.
A formatter replaces the built-in display, so precision and decimalSeparator no longer shape what the field shows. Pad the decimals inside your formatter, with toFixed, if you need them.
Values a JavaScript number cannot hold
stringMode keeps the value a string end to end, so an amount past Number.MAX_SAFE_INTEGER or a rate with eighteen decimals is not rounded.
Use it for very large or very precise values: token amounts, scientific readings, financial values a backend already sends as a string.
Type the value as a string with stringMode. Write <InputNumber<string>> so value, onChange, min, max and step are typed as strings. Pass long values as strings too, since a long number literal is rounded before the field sees it.
Do not turn it on by default. Everything downstream, including arithmetic in your own form, then has to handle strings.
Stepping: keyboard, wheel and buttons
There are three ways to change the value without typing it, and each has its own prop. Turning one off leaves the others on.
Arrow keys are on by default. Up and Down step by step, and Shift steps by ten times step. keyboard={false} turns off arrow stepping only. Enter still commits the field and fires onPressEnter.
The buttons are on by default (controls defaults to true). Holding one down repeats the step.
The wheel is off by default. changeOnWheel turns it on, and it only acts while the field has focus. Leave it off in anything that scrolls, or a reader scrolling past a focused field changes a value they were not editing.
controls={false} suits a value that is stated rather than nudged, like a year, an amount or an account figure.
The buttons are removed for you when the field is disabled or readOnly.
The buttons are not in the tab order, so with keyboard={false} typing is the only way left to change the value from the keyboard. Only turn off arrow stepping where typing covers the whole job.
Spinner mode
mode="spinner" puts a minus and a plus on either side of the value. Use it for small counts people nudge, like guests, rooms or a cart quantity, and on touch screens, where the default stacked arrows are hard to hit.
Give stacked spinners a fixed width. A spinner is as wide as its content, so a column of them ends at different places as the digits change. Set the same width on each one, like w-32.
Name a field whose label sits elsewhere. When the visible label is in another column or cell, pass aria-label. Without it a screen reader announces an unnamed spin button.
Prefix, suffix and addons
Four props put something beside the number, and two of them are deprecated.
prefix and suffix sit inside the field, on the same background as the text. Use them for the unit that belongs to the number: a currency symbol before, a % or kg after.
Keep them to a symbol or one short word, and keep them static. An affix takes no focus, so nothing interactive belongs there.
addonBefore and addonAfter put a separately bordered block outside the field, and both are deprecated. They still work, but new code should not use them.
Use Space.Compact for a unit selector. Put the field and a Select in Space.Compact. The two join into one shape, and each stays its own control.
Variant and status
variant sets the field's container: outlined (the default), filled, borderless, underlined. Pick one per form and keep it, since two in one column read as two kinds of field.
borderless is for a field inside something that already has an edge, like a table cell or a toolbar. On a plain background it stops looking editable.
status takes error, warning, success and validating, and only paints a color. Inside a Form.Item the form sets the status and the message together, so leave the prop alone. Outside one, render the message yourself.
bordered is deprecated in favor of variant. bordered={false} is variant="borderless".
Styling it
Style a part through its classNames or styles key before reaching for a descendant selector: root, prefix, suffix, input, actions. The keys are part of the component's API, so they survive upgrades that rename internal classes.
actions is the button column, and it only exists in mode="input". In spinner mode the two buttons flank the field instead.
input is the element the reader types into. Put text alignment and tabular figures there rather than on root. A quantity column reads better right-aligned with tabular-nums.