Open UI

NumberField

A field that allows users to enter a number and increment or decrement the value using stepper buttons.

import { NumberField } from "@opengovsg/oui"export const Example = () => {  return (    <NumberField      aria-label="Enter your favourite number"      inputProps={{        placeholder: "Enter your favourite number",      }}    />  )}

Usage

Use NumberField for numeric input with optional stepper buttons. Use TextField with type="tel" or a custom pattern if you need digit-only input without steppers.

import { NumberField } from "@opengovsg/oui"
<NumberField aria-label="Enter quantity" />

Alternatively, install the component as local source via the shadcn CLI:

npx shadcn@latest add https://oui.open.gov.sg/r/number-field.json
pnpm dlx shadcn@latest add https://oui.open.gov.sg/r/number-field.json
npx shadcn@latest add https://oui.open.gov.sg/r/number-field.json
bunx --bun shadcn@latest add https://oui.open.gov.sg/r/number-field.json

The NumberField component provides an accessible way for users to enter numeric values with optional stepper buttons for incrementing and decrementing the value.

If the component does not have a visible label (by passing a label prop), an aria-label or aria-labelledby prop must be passed instead to identify it to assistive technology.

Examples

With Label and Description

Provide clear instructions to users with labels and descriptions.

import { NumberField } from "@opengovsg/oui"export const Example = () => {  return (    <NumberField      label="Quantity"      description="Enter the quantity you want to order"    />  )}

With Placeholder

Use the inputProps.placeholder to show placeholder text.

import { NumberField } from "@opengovsg/oui"export const Example = () => {  return (    <NumberField      label="Age"      inputProps={{        placeholder: "Enter your age",      }}    />  )}

Hide Steppers

Use the hideSteppers prop to hide the increment and decrement buttons.

import { NumberField } from "@opengovsg/oui"export const Example = () => {  return (    <NumberField      label="Amount"      hideSteppers      inputProps={{        placeholder: "Enter amount",      }}    />  )}

Start and End Content

Add custom content to the start or end of the input field using startContent and endContent props.

import { NumberField } from "@opengovsg/oui"export const Example = () => {  return (    <NumberField      label="Price"      defaultValue={100}      startContent={        <div className="pointer-events-none flex items-center">          <span className="text-interaction-main-default">$</span>        </div>      }      endContent={        <div className="flex items-center">          <label className="sr-only" htmlFor="currency">            Currency          </label>          <select            aria-label="Select currency"            className="text-interaction-main-default border-0 bg-transparent outline-transparent outline-solid"            defaultValue="SGD"            id="currency"            name="currency"          >            <option aria-label="SG Dollar" value="SGD">              SGD            </option>            <option aria-label="US Dollar" value="USD">              USD            </option>            <option aria-label="Euro" value="EUR">              EUR            </option>          </select>        </div>      }    />  )}

Min and Max Values

Use minValue and maxValue props to restrict the range of acceptable values.

import { NumberField } from "@opengovsg/oui"export const Example = () => {  return (    <NumberField      label="Quantity"      description="Min: 0, Max: 100"      minValue={0}      maxValue={100}      defaultValue={0}    />  )}

Step Value

Use the step prop to define the increment/decrement step size.

import { NumberField } from "@opengovsg/oui"export const Example = () => {  return (    <NumberField      label="Volume"      description="Adjust in increments of 5"      step={5}      defaultValue={50}    />  )}

Format Options

Use the formatOptions prop to format the number display with internationalization support.

import { NumberField } from "@opengovsg/oui"export const Example = () => {  return (    <div className="flex flex-col gap-4">      <NumberField        label="Currency"        defaultValue={1000}        formatOptions={{          style: "currency",          currency: "USD",        }}      />      <NumberField        label="Percentage"        defaultValue={0.5}        formatOptions={{          style: "percent",        }}      />      <NumberField        label="Unit"        defaultValue={10}        formatOptions={{          style: "unit",          unit: "kilogram",        }}      />    </div>  )}

Controlled

Use the value and onChange props to control the number field value programmatically.

import { useState } from "react"import { NumberField } from "@opengovsg/oui"export const Example = () => {  const [value, setValue] = useState(25)  return (    <div className="flex flex-col gap-4">      <NumberField        label="Controlled Number Field"        value={value}        onChange={setValue}      />      <p className="text-sm text-gray-600">Current value: {value}</p>    </div>  )}

With Error Message

Combine isInvalid and errorMessage props to show validation errors.

import { NumberField } from "@opengovsg/oui"export const Example = () => {  return (    <NumberField      label="Age"      minValue={18}      maxValue={100}      defaultValue={15}      isInvalid      errorMessage="Age must be between 18 and 100"    />  )}

Sizes

Use the size prop to change the size of the number field.

import { NumberField } from "@opengovsg/oui"export const Example = () => {  return (    <div className="flex flex-col gap-6">      <NumberField        size="xs"        label="Extra Small"        inputProps={{ placeholder: "Extra small size" }}      />      <NumberField        size="sm"        label="Small"        inputProps={{ placeholder: "Small size" }}      />      <NumberField        size="md"        label="Medium (default)"        inputProps={{ placeholder: "Medium size" }}      />    </div>  )}

Disabled

Use the isDisabled prop to disable the number field.

import { NumberField } from "@opengovsg/oui"export const Example = () => {  return (    <NumberField      label="Quantity"      isDisabled      inputProps={{        placeholder: "This field is disabled",      }}    />  )}

Read Only

Use the isReadOnly prop to make the number field read-only.

import { NumberField } from "@opengovsg/oui"export const Example = () => {  return (    <NumberField      label="Order Total"      isReadOnly      defaultValue={150}      description="This value cannot be changed"    />  )}

Required

Use the isRequired prop to mark the field as required.

import { NumberField } from "@opengovsg/oui"export const Example = () => {  return (    <NumberField      label="Quantity"      isRequired      description="This field is required"    />  )}

Disable Wheel

By default, users can change the value using the mouse wheel. Use isWheelDisabled to prevent this.

import { NumberField } from "@opengovsg/oui"export const Example = () => {  return (    <NumberField      label="Price"      isWheelDisabled      defaultValue={100}      description="Mouse wheel scrolling is disabled"    />  )}

Validation

See the Forms guide for end-to-end patterns including React Hook Form + Zod.

The NumberField component supports built-in validation:

  • Range validation: Automatically validates against minValue and maxValue
  • Required validation: Use isRequired to make the field mandatory
  • Custom validation: Use the validate prop for custom validation logic

Accessibility

  • Built with a native <input type="number"> element
  • Full keyboard support (arrow keys to increment/decrement, typing to enter values)
  • Proper ARIA labeling and descriptions
  • Required and invalid states exposed to assistive technology
  • Stepper buttons are properly labeled for screen readers

Slots

  • base: The root container wrapper
  • label: The label text element
  • field: The field group wrapper containing input and steppers
  • input: The input element
  • stepperContainer: Container for the stepper buttons
  • increment: The increment button
  • decrement: The decrement button
  • description: The description text below the field
  • error: The error message text

Data Attributes

NumberField has the following attributes on the base element, which you can use to style the component based on its state (e.g. group-[data-invalid=true]:bg-red-500):

  • data-invalid: When the input is invalid. Based on isInvalid prop.
  • data-required: When the input is required. Based on isRequired prop.
  • data-readonly: When the input is readonly. Based on isReadOnly prop.
  • data-focus: When the input is being focused.
  • data-focus-within: When the input is being focused or any of its children.
  • data-focus-visible: When the input is being focused with the keyboard.
  • data-disabled: When the input is disabled. Based on isDisabled prop.
  • data-hide-steppers: When the steppers are hidden. Based on hideSteppers prop.
  • data-has-start-content: When the input has start content. Based on startContent prop.
  • data-has-end-content: When the input has end content. Based on endContent prop.

Custom Styles

You can customize the NumberField component by passing custom Tailwind CSS classes to the component slots via the classNames prop.

<NumberField
  label="Price"
  classNames={{
    base: "max-w-xs",
    input: "text-right",
    label: "font-bold",
  }}
/>

Props

NumberField

PropTypeDefaultDescription
labelstring-The label for the number field
descriptionstring-The description text shown below the field
errorMessagestring | ((validation: ValidationResult) => string)-The error message to display when validation fails
valuenumber-The current value (controlled)
defaultValuenumber-The default value (uncontrolled)
onChange(value: number) => void-Callback fired when the value changes
minValuenumber-The minimum allowed value
maxValuenumber-The maximum allowed value
stepnumber1The amount to increment/decrement when using steppers
formatOptionsIntl.NumberFormatOptions-Formatting options for number display
hideSteppersbooleanfalseWhether to hide the stepper buttons
startContentReact.ReactNode-Content to display at the start of the input
endContentReact.ReactNode-Content to display at the end of the input
size"xs" | "sm" | "md""md"The size of the component
isDisabledbooleanfalseWhether the field is disabled
isReadOnlybooleanfalseWhether the field is read-only
isRequiredbooleanfalseWhether the field is required
isInvalidbooleanfalseWhether the field should display as invalid
isWheelDisabledbooleanfalseWhether to disable value changes via mouse wheel
validate(value: number) => ValidationError | true | null | undefined-Custom validation function
inputPropsPartial<InputProps>-Additional props to pass to the input element
classNamesSlotsToClasses<NumberFieldSlots>-Custom CSS classes for component slots

On this page