Skip to content
v2.5.0
English
Русский
Documentation

NumberField

NumberField is a compound component for numeric entry with optional stepper buttons, bounds, masking, and wheel or keyboard stepping.
number-field.tsx · 16 lines
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
Tested in
39 tests

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

  • NumberFieldProps
  • NumberFieldInputProps
  • NumberFieldIncrementProps
  • NumberFieldDecrementProps
  • NumberFieldSize

Minimal Composition

seat-count.tsx · 26 lines
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 container
  • NumberFieldInput: numeric text input

Optional Parts

  • NumberFieldIncrementButton: step up control
  • NumberFieldDecrementButton: step down control

Composition Order

  1. NumberField
  2. NumberFieldDecrementButton
  3. NumberFieldInput
  4. NumberFieldIncrementButton

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.

seat-count.tsx · 26 lines
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.

en-US
ru-RU
ar-EG
he-IL
order-total.tsx · 41 lines
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.

form-submit-and-reset.tsx · 44 lines
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.

cart-line-items.tsx · 76 lines
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.

seats-below-active-members.tsx · 39 lines
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.

locked-until-override.tsx · 41 lines
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.

en-US
ru-RU
ar-EG
he-IL
discount-percentage.tsx · 35 lines
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.

large-amount-no-wheel.tsx · 38 lines
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

  1. NumberField
  2. optional decrement button
  3. NumberFieldInput
  4. optional increment button

Valid Composition Patterns

seat-count.tsx · 26 lines
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.