Number Field
A numeric input with increment and decrement buttons, formatting, validation, and scrub controls.
API Reference
Original primitive API
Behavior, accessibility details, and low-level props are documented by Base UI.
Basic
import { Field, FieldLabel, NumberField,} from "moduix";import { useId } from "react";export function NumberFieldDemo() { const id = useId(); return ( <Field> <FieldLabel htmlFor={id}>Amount</FieldLabel> <NumberField id={id} defaultValue={100} /> </Field> );}Full list of component variables available for project-level overrides.
| Property | Default | Description |
|---|---|---|
| --number-field-border-color | var(--color-border) | Controls default border color. |
| --number-field-border-color-invalid | var(--color-destructive) | Controls invalid input border and focus ring color. |
| --number-field-border-style | solid | Controls border style for buttons and input. |
| --number-field-border-width | var(--border-width-sm) | Controls border width for buttons and input. |
| --number-field-button-bg | var(--color-background) | Controls button background. |
| --number-field-button-bg-active | var(--number-field-button-bg-hover) | Controls button background while pressed. |
| --number-field-button-bg-hover | var(--color-accent) | Controls button background on hover. |
| --number-field-button-color | var(--color-foreground) | Controls button icon color. |
| --number-field-control-height | var(--size-lg) | Controls input and button height. |
| --number-field-disabled-opacity | var(--opacity-disabled) | Controls disabled opacity. |
| --number-field-focus-ring-color | var(--color-ring) | Controls focus ring color. |
| --number-field-focus-ring-width | var(--number-field-border-width) | Controls focus ring width. |
| --number-field-gap | var(--spacing-1) | Controls spacing between number field parts. |
| --number-field-icon-size | 0.875rem | Controls button icon size. |
| --number-field-input-bg | var(--color-background) | Controls input background. |
| --number-field-input-color | var(--color-foreground) | Controls input text color. |
| --number-field-input-font-size | var(--text-md) | Controls input font size. |
| --number-field-input-line-height | var(--line-height-text-md) | Controls input line height. |
| --number-field-input-padding-x | 0.75rem | Controls input horizontal padding. |
| --number-field-input-padding-y | 0.5rem | Controls input vertical padding. |
| --number-field-input-width | 6rem | Controls input width. |
| --number-field-max-width | none | Controls the root number field max width. |
| --number-field-radius | var(--radius-md) | Controls the outer control corner radius. |
| --number-field-scrub-area-cursor-size | 1.5rem | Controls custom scrub cursor size. |
| --number-field-scrub-area-color | var(--color-foreground) | Controls scrub area text color. |
| --number-field-scrub-area-gap | var(--spacing-2) | Controls scrub area spacing. |
| --number-field-width | auto | Controls the root number field width. |
Interactive variables scoped for docs preview without changing size scale tokens.
| Property | Value | Default | Description |
|---|---|---|---|
| --number-field-border-color | var(--color-border) | Controls default border color. | |
| --number-field-border-width | var(--border-width-sm) | Controls default border width. | |
| --number-field-button-bg | var(--color-background) | Controls button background. | |
| --number-field-button-bg-hover | var(--color-accent) | Controls button hover background. | |
| --number-field-button-color | var(--color-foreground) | Controls button icon color. | |
| --number-field-focus-ring-color | var(--color-ring) | Controls focus ring color. | |
| --number-field-input-bg | var(--color-background) | Controls input background. | |
| --number-field-input-color | var(--color-foreground) | Controls input text color. | |
| --number-field-radius | var(--radius-md) | Controls control corner radius. |
Anatomy
NumberField keeps numeric state on the root and renders the standard decrement, input, and
increment group automatically. Add NumberFieldScrubArea only when the label or another visible
part should support drag-to-change behavior.
Field
├─ FieldLabel
└─ NumberField
├─ NumberFieldScrubArea
│ ├─ scrub label
│ └─ cursor (internal)
└─ NumberFieldGroup
├─ NumberFieldDecrement
├─ NumberFieldInput
└─ NumberFieldIncrement<Field>
<FieldLabel htmlFor={id}>Amount</FieldLabel>
<NumberField id={id} defaultValue={100} />
</Field>| Part | Role |
|---|---|
NumberField | Root state machine. Controls value, formatting, validation, min/max, step, and change handlers. |
NumberFieldScrubArea | Optional drag area for changing the value. It renders the scrub cursor internally by default. |
NumberFieldGroup | Visual wrapper for the stepper buttons and input. Rendered automatically unless withGroup={false} is set. |
NumberFieldDecrement | Button that decreases the value. It renders the default minus icon, or your custom icon as children. |
NumberFieldInput | Editable numeric input. It receives formatted text and invalid, disabled, and readonly state attributes. |
NumberFieldIncrement | Button that increases the value. It renders the default plus icon, or your custom icon as children. |
NumberField does not use portal-like service layers such as portal, backdrop, or viewport.
The standard group is rendered internally by default and can be styled with classNames.group,
classNames.decrement, classNames.input, and classNames.increment. The scrub cursor is an
internal service slot of NumberFieldScrubArea, so it is customized through cursor,
withCursor, and classNames.cursor instead of being added to the public composition.
Composition
Use NumberField for root behavior props such as value, defaultValue, onValueChange, min,
max, step, required, disabled, readOnly, and format.
Use the default group for standard number inputs. Set decrementLabel and incrementLabel to
localize the internal button labels, or set withGroup={false} and compose NumberFieldGroup,
NumberFieldDecrement, NumberFieldInput, and NumberFieldIncrement manually when the layout or
icons need full control.
Visible parts accept className: NumberField, NumberFieldScrubArea, NumberFieldGroup,
NumberFieldDecrement, NumberFieldInput, and NumberFieldIncrement. NumberFieldScrubArea
also accepts cursor for replacing the default scrub cursor icon, withCursor={false} for hiding
it, and classNames.cursor for styling the internal cursor wrapper.
Examples
Controlled
Control the numeric value from React state when other UI needs to react to the current number.
import { Field, FieldLabel, NumberField,} from "moduix";import { useState, useId } from "react";export function ControlledNumberFieldDemo() { const id = useId(); const [value, setValue] = useState(24 as number | null); return ( <div> <Field> <FieldLabel htmlFor={id}>Controlled value</FieldLabel> <NumberField id={id} value={value} onValueChange={setValue} /> </Field> <span>Current value: {value ?? "empty"}</span> </div> );}Min, Max, and Step
Use min, max, and step to constrain button, keyboard, scrub, and enabled wheel-scrub interactions.
import { Field, FieldLabel, NumberField,} from "moduix";import { useId } from "react";export function MinMaxStepNumberFieldDemo() { const id = useId(); return ( <Field> <FieldLabel htmlFor={id}>Quantity (0-20, step 2)</FieldLabel> <NumberField id={id} defaultValue={10} min={0} max={20} step={2} /> </Field> );}Formatting
Use format for localized presentation while keeping the value numeric.
import { Field, FieldLabel, NumberField,} from "moduix";import { useId } from "react";export function FormattedNumberFieldDemo() { const id = useId(); return ( <Field> <FieldLabel htmlFor={id}>Price</FieldLabel> <NumberField id={id} defaultValue={1250} min={0} step={50} format={{ style: "currency", currency: "USD", maximumFractionDigits: 0 }} /> </Field> );}Scrub Area
Add NumberFieldScrubArea when users should be able to drag the label area to change the value. The scrub cursor renders automatically and can be customized with the cursor, withCursor, and classNames.cursor props.
import { NumberField, NumberFieldScrubArea, FieldLabel,} from "moduix";import { useId } from "react";export function ScrubAreaNumberFieldDemo() { const id = useId(); return ( <NumberField id={id} defaultValue={250}> <NumberFieldScrubArea> <FieldLabel htmlFor={id}>Drag to scrub</FieldLabel> </NumberFieldScrubArea> </NumberField> );}Field Validation
Wrap the control in Field to show native validation errors from required, min, and max.
import { Field, FieldLabel, NumberField, FieldError,} from "moduix";import { useId } from "react";export function NumberFieldValidationDemo() { const id = useId(); return ( <Field name="quantity" validationMode="onBlur"> <FieldLabel htmlFor={id}>Items</FieldLabel> <NumberField id={id} min={1} max={10} required /> <FieldError match="valueMissing">Please provide a number.</FieldError> <FieldError match="rangeUnderflow">Value should be at least 1.</FieldError> <FieldError match="rangeOverflow">Value should be at most 10.</FieldError> </Field> );}Custom Icons
Pass children to the stepper buttons to use icons from your application or icon library.
import { Field, FieldLabel, NumberField, NumberFieldGroup, NumberFieldDecrement, ChevronDownIcon, NumberFieldInput, NumberFieldIncrement, ChevronUpIcon,} from "moduix";import { useId } from "react";import styles from "./number-field.module.css";export function CustomIconsNumberFieldDemo() { const id = useId(); return ( <Field> <FieldLabel htmlFor={id}>Floors</FieldLabel> <NumberField id={id} defaultValue={8} withGroup={false}> <NumberFieldGroup> <NumberFieldDecrement aria-label="Decrease value" className={styles.customButton}> <ChevronDownIcon /> </NumberFieldDecrement> <NumberFieldInput className={styles.customInput} /> <NumberFieldIncrement aria-label="Increase value" className={styles.customButton}> <ChevronUpIcon /> </NumberFieldIncrement> </NumberFieldGroup> </NumberField> </Field> );}.customButton { --number-field-button-bg: var(--color-muted); --number-field-button-bg-hover: var(--color-accent); --number-field-icon-size: 1rem;}.customInput { --number-field-input-width: 7rem; --number-field-input-font-size: var(--text-lg);}