import {
Label,
NumberField,
NumberFieldDecrementButton,
NumberFieldIncrementButton,
NumberFieldInput,
} from '@vega-ui/react';
<div>
<Label htmlFor='preview-guests'>Guests</Label>
<NumberField min={1} max={10} defaultValue={2}>
<NumberFieldDecrementButton aria-label='Remove guest' />- Browser support
- Newly available 2024
- Chrome 125
- Edge 125
- Firefox 128
- Safari 18
- Safari on iOS 18
- Bundle size
Summary
NumberField is a compound component for numeric entry with optional stepper buttons, bounds, masking, and wheel or keyboard stepping. It is for exact numeric values, not just any text that happens to contain digits.
Imports
import {
NumberField,
NumberFieldDecrementButton,
NumberFieldIncrementButton,
NumberFieldInput,
type NumberFieldDecrementProps,
type NumberFieldIncrementProps,
type NumberFieldInputProps,
type NumberFieldProps,
type NumberFieldSize,
} from '@vega-ui/react';Exported Types
NumberFieldPropsNumberFieldInputPropsNumberFieldIncrementPropsNumberFieldDecrementPropsNumberFieldSize
Minimal Composition
import {
HelperText,
Label,
NumberField,
NumberFieldDecrementButton,
NumberFieldIncrementButton,
NumberFieldInput,
} from '@vega-ui/react';
import { useId } from 'react';
export default function SeatCount() {
const id = useId();Required Parts
NumberField: root state containerNumberFieldInput: numeric text input
Optional Parts
NumberFieldIncrementButton: step up controlNumberFieldDecrementButton: step down control
Composition Order
NumberFieldNumberFieldDecrementButtonNumberFieldInputNumberFieldIncrementButton
Variants
- Size:
sm,md,lg - Value model: controlled or uncontrolled
- Interaction: typed entry, step buttons, arrow keys, optional mouse wheel
Edge Cases
- Typed values can be temporarily empty or incomplete during editing.
- Step buttons, typed input, and bounds logic should stay in sync.
- Wheel-based changes can be surprising in dense forms if enabled without care.
- The same numeric policy should drive typing, stepping, clamping, and validation instead of letting each path behave differently.
Common Mistakes
- Treating every intermediate typed value as a valid final number.
- Forgetting bounds handling when step buttons are present.
- Using it where a free-form text field would be clearer.
Examples
Basic
Basic: seat count with bounds
min, max and defaultValue live on the root; id, name and aria-describedby go on NumberFieldInput. The stepper buttons have no built-in name, so each gets an aria-label, and they disable themselves at the bounds.
import {
HelperText,
Label,
NumberField,
NumberFieldDecrementButton,
NumberFieldIncrementButton,
NumberFieldInput,
} from '@vega-ui/react';
import { useId } from 'react';
export default function SeatCount() {
const id = useId();Controlled/Stateful
Controlled/Stateful: quantity drives the order total
value and onChangeValue work with numbers, not strings. An emptied field reports NaN, so the total checks Number.isNaN before formatting, and the buttons below set the value from outside, including clearing it.
import {
Button,
Label,
NumberField,
NumberFieldDecrementButton,
NumberFieldIncrementButton,
NumberFieldInput,
Text,
} from '@vega-ui/react';
import { useId, useState } from 'react';
const UNIT_PRICE = 24.5;Form/Integration
Form/Integration: submit and reset in a form
Put name on NumberFieldInput. FormData returns the field text, which contains thousands separators, so strip whitespace before converting it to a number. Reset restores defaultValue.
import {
Button,
Label,
NumberField,
NumberFieldDecrementButton,
NumberFieldIncrementButton,
NumberFieldInput,
Text,
} from '@vega-ui/react';
import { FormEvent, useId, useState } from 'react';
export default function FormSubmitAndReset() {Layout/Overlay
Layout/Overlay: quantities in a cart
Compact size='sm' steppers in repeated rows. Every input and button gets an aria-label that names its product, because "Quantity" alone is ambiguous when there are several rows. The subtotal skips emptied rows.
import {
NumberField,
NumberFieldDecrementButton,
NumberFieldIncrementButton,
NumberFieldInput,
Text,
} from '@vega-ui/react';
import { useState } from 'react';
interface LineItem {
sku: string;
name: string;Error
Error: below a business minimum
The mask keeps the value inside min/max, but rules like "not fewer seats than active members" are yours. The error is derived from the value, set with error on the root and aria-invalid on the input, and it clears once the value is valid again.
import {
HelperText,
Label,
NumberField,
NumberFieldDecrementButton,
NumberFieldIncrementButton,
NumberFieldInput,
} from '@vega-ui/react';
import { useId, useState } from 'react';
const ACTIVE_MEMBERS = 12;
Disabled
Disabled: locked until an override is checked
disabled on the root disables the input and both buttons. A checkbox unlocks the field, and the hint explains why it is locked.
import {
Checkbox,
CheckboxCheckedIcon,
CheckboxIndicator,
CheckboxInput,
HelperText,
Label,
NumberField,
NumberFieldDecrementButton,
NumberFieldIncrementButton,
NumberFieldInput,
} from '@vega-ui/react';Edge
Edge: fractional step with a unit suffix
step={0.5} with maximumFractionDigits={1} steps without floating-point noise. The % suffix sits between the input and the increment button and is hidden from screen readers; the derived price below is linked with aria-describedby.
import {
Label,
NumberField,
NumberFieldDecrementButton,
NumberFieldIncrementButton,
NumberFieldInput,
Text,
} from '@vega-ui/react';
import { useId, useState } from 'react';
const LIST_PRICE = 1290;
export const locales = ['en-US', 'ru-RU', 'ar-EG', 'he-IL'];Edge: large amounts without wheel changes
Large values get thousands separators as you type. changeOnWheel={false} stops the mouse wheel from changing a focused amount when people scroll a long form.
import {
HelperText,
Label,
NumberField,
NumberFieldDecrementButton,
NumberFieldIncrementButton,
NumberFieldInput,
} from '@vega-ui/react';
import { useId, useState } from 'react';
const compact = new Intl.NumberFormat('en-US', { notation: 'compact', maximumFractionDigits: 1 });
Anatomy
Overview
NumberField extends the TextField model with numeric masking, bounds, step behavior, and optional stepper buttons.
Required Parts
NumberField
Required. Owns numeric constraints, derived boundary state, and stepping behavior.
NumberFieldInput
Required. Renders the actual input and syncs with numeric context.
Optional Parts
NumberFieldIncrementButton
Optional. Steps the value up.
NumberFieldDecrementButton
Optional. Steps the value down.
Composition Order
NumberField- optional decrement button
NumberFieldInput- optional increment button
Valid Composition Patterns
import {
HelperText,
Label,
NumberField,
NumberFieldDecrementButton,
NumberFieldIncrementButton,
NumberFieldInput,
} from '@vega-ui/react';
import { useId } from 'react';
export default function SeatCount() {
const id = useId();Invalid Composition Patterns
Buttons without the input
The stepper controls exist, but there is no editable numeric value.
External numeric logic fighting the internal bounds model
Conflicting clamping or parsing rules create surprising values.
Free-form text assumptions
The control is numeric-first and should not be treated as a generic text field.