Number Field

A numeric input with steppers, a scrub-to-change label, keyboard stepping and Intl formatting. It is an Input underneath, so it sizes, focuses and joins an InputGroup like any other field.

Drag the label or use the arrow keys.

Installation#

# One-time setup (tokens + lib helpers)npx koalaui-cli@latest init# Add this component (its dependencies come along)npx koalaui-cli@latest add number-field

Usage#

usage.tsxTSX
import {  NumberField,  NumberFieldGroup,  NumberFieldInput,  NumberFieldIncrement,  NumberFieldDecrement,} from "@/components/ui/number-field"import { Field, FieldLabel } from "@/components/ui/field"export function Example() {  return (    <Field>      <FieldLabel>Seats</FieldLabel>      <NumberField defaultValue={5} min={1} max={50}>        <NumberFieldGroup>          <NumberFieldInput />          <NumberFieldDecrement />          <NumberFieldIncrement />        </NumberFieldGroup>      </NumberField>    </Field>  )}

Anatomy#

Every part but the group and the input is optional. Order the steppers however you like: trailing, or one on each side.

<NumberField>  <NumberFieldScrubArea>    <FieldLabel />    <NumberFieldScrubAreaCursor />  </NumberFieldScrubArea>  <NumberFieldGroup>    <NumberFieldDecrement />    <NumberFieldInput />    <NumberFieldIncrement />  </NumberFieldGroup></NumberField>

Basic#

The value is a number, never a string. Type, press the arrow keys, or press the steppers. Hold a stepper and it repeats after a short pause.

Min, max and step#

Steps clamp to min and max, and a typed value clamps on blur. The stepper at a bound disables itself. Shift and PageUp/PageDown take largeStep, Alt takes smallStep, and Home/End jump to the bounds.

0 to 100, in steps of 5.

Formatting#

Pass Intl.NumberFormatOptions to format. Typed text is read back in the same locale, so 1.299,50 € and $1,299.50 both parse. A percent field stores the fraction: 25% is 0.25.

Scrub area#

Wrap the label in NumberFieldScrubArea and drag it. With a mouse the pointer locks, so the drag never runs off screen, and NumberFieldScrubAreaCursor draws the cursor in its place. Safari keeps the native resize cursor instead.

Quantity#

Put the steppers on either side and center the figure for a cart or a guest count.

Field#

Inside a Field, the label, the description and the error state wire themselves: FieldLabel names the input, FieldHint describes it, and hasError turns the frame destructive.

Up to 12 per booking.

Enter a deposit above $0.

Input group#

NumberFieldGroup is an Input frame, so InputGroup strips it into a segment. Keep NumberField outside the group and the group's size reaches the field.

kg

Sizes#

The same three heights as Input: sm (32 px), md (36 px) and lg (40 px), set on NumberFieldGroup. Unset, it follows the container (a Form imposes lg) or the density (sm under compact, md under comfortable). The steppers scale with it.

Disabled and read-only#

disabled dims the field and drops it from the form. readOnly keeps it focusable and readable but fixes the value, so the steppers and the keys do nothing.

Controlled#

onValueChange fires on every keystroke and step. onValueCommitted fires once the change settles, which is the one to save on.

value: 40 · committed: 40

API reference#

NumberField

The root. Owns the value, the steps and the formatting, and stacks a scrub-area label over the group. Reads id, disabled and required from a surrounding Field.

PropTypeDefaultDescription
valuenumber | null-The value, controlled. null is an empty field.
defaultValuenumber | nullnullThe initial value when uncontrolled.
onValueChange(value: number | null) => void-Fires on every change: typing, stepping, scrubbing, the wheel.
onValueCommitted(value: number | null) => void-Fires when a change settles: on blur, on Enter, after a key step, a press or a scrub.
minnumber-The lowest value. Steps clamp to it, typed values clamp on blur, Home jumps to it.
maxnumber-The highest value. End jumps to it.
stepnumber1What the arrow keys and the steppers add.
smallStepnumber0.1The step with Alt held.
largeStepnumber10The step with Shift held, and for PageUp / PageDown.
formatIntl.NumberFormatOptions-How the value is shown: currency, percent, units, fraction digits. Typed text is parsed back with the same rules.
localestring-The formatting locale. Unset, it is en-US on the server and the visitor's own after hydration.
disabledbooleanfalseDims the group and ignores input.
readOnlybooleanfalseKeeps the value readable and focusable, but fixed.
requiredbooleanfalseMarks the input required.
namestring-Submits the raw number, not the formatted text, in a hidden input.
idstring-The input's id. Defaults to the Field's.
allowWheelScrubbooleanfalseLets the mouse wheel step the value while the input has focus.
Data attributeDescription
data-disabledPresent when disabled.
data-readonlyPresent when read-only.
data-requiredPresent when required.
data-scrubbingPresent while the scrub area is being dragged.

NumberFieldGroup

The field frame. It is an InputRoot and keeps its input-root slot, so every Input prop and every InputGroup rule applies.

PropTypeDefaultDescription
size"sm" | "md" | "lg"-32, 36 or 40px. Unset, it follows the container, then the density.
density"compact" | "comfortable"-Picks the default size: compact is sm, comfortable is md.
variant"default" | "inline""default"inline hides the frame until hover.
hasErrorboolean-The destructive border and ring. Defaults to the Field's.
Data attributeDescription
data-scrubbingPresent while the scrub area is being dragged.

NumberFieldInput

The text input, with role="spinbutton", aria-valuenow, aria-valuemin, aria-valuemax and aria-valuetext. Takes every input prop except the value ones.

NumberFieldIncrement

A small quiet button that steps up. Press and hold to repeat. It disables itself at max and stays out of the tab order, since the arrow keys do the same. Children replace the plus glyph.

NumberFieldDecrement

The same button, stepping down and disabling at min.

NumberFieldScrubArea

Wrap a label in it to drag the value. With a mouse it takes the pointer lock, so the drag never runs off screen.

PropTypeDefaultDescription
direction"horizontal" | "vertical""horizontal"The drag axis. Right and up increase.
pixelSensitivitynumber2Pixels of travel per step. Higher is slower.
Data attributeDescription
data-scrubbingPresent while dragging.

NumberFieldScrubAreaCursor

The cursor shown while the pointer is locked, when the system cursor is hidden. Renders nothing otherwise. Children replace the arrows glyph.

FAQ#

`NumberInput` is a single prop-driven control with plain numbers. `NumberField` is composed from parts and adds Intl formatting, step modifiers, a scrub area and committed events. Reach for it when the number has a unit, a currency or a percent.