Skip to main content
0.79.0
View Zag.js on Github
Join the Discord server

Number Input

The number input provides controls for editing, incrementing or decrementing numeric values using the keyboard or pointer.

Properties

Features

  • Based on the spinbutton pattern.
  • Supports using the scroll wheel to increment and decrement the value.
  • Handles floating point rounding errors when incrementing, decrementing, and snapping to step.
  • Supports pressing and holding the spin buttons to continuously increment or decrement.
  • Supports rounding value to specific number of fraction digits.
  • Support for scrubbing interaction.

Installation

To use the number input machine in your project, run the following command in your command line:

npm install @zag-js/number-input @zag-js/react # or yarn add @zag-js/number-input @zag-js/react

This command will install the framework agnostic number input logic and the reactive utilities for your framework of choice.

Anatomy

To set up the number input correctly, you'll need to understand its anatomy and how we name its parts.

Each part includes a data-part attribute to help identify them in the DOM.

Usage

First, import the number input package into your project

import * as numberInput from "@zag-js/number-input"

The number input package exports two key functions:

  • machine — The state machine logic for the number input widget as described in the WAI-ARIA spinner pattern.
  • connect — The function that translates the machine's state to JSX attributes and event handlers.

You'll need to provide a unique id to the useMachine hook. This is used to ensure that the every part has a unique identifier.

Next, import the required hooks and functions for your framework and use the number input machine in your project 🔥

import * as numberInput from "@zag-js/number-input" import { useMachine, normalizeProps } from "@zag-js/react" export function NumberInput() { const [state, send] = useMachine(numberInput.machine({ id: "1" })) const api = numberInput.connect(state, send, normalizeProps) return ( <div {...api.getRootProps()}> <label {...api.getLabelProps()}>Enter number:</label> <div> <button {...api.getDecrementTriggerProps()}>DEC</button> <input {...api.getInputProps()} /> <button {...api.getIncrementTriggerProps()}>INC</button> </div> </div> ) }

Setting the initial value

To set the initial value of the number input, you can set the value context property.

Note: The value must be a string

const [state, send] = useMachine( numberInput.machine({ value: "13", }), )

Setting a minimum and maximum value

Pass the min prop or max prop to set an upper and lower limit for the input. By default, the input will restrict the value to stay within the specified range.

const [state, send] = useMachine( numberInput.machine({ min: 10, max: 200, }), )

To allow the value overflow the specified min or max, set the allowOverflow: true in the context.

Scrubbing the input value

The number input machine supports the scrubber interaction pattern. The use this pattern, spread the scrubberProps from the api on to the scrubbing element.

It uses the Pointer lock API and tracks the pointer movement. It also renders a virtual cursor which mimicks the real cursor's pointer.

import * as numberInput from "@zag-js/number-input" import { useMachine, normalizeProps } from "@zag-js/react" export function NumberInput() { const [state, send] = useMachine(numberInput.machine({ id: "1" })) const api = numberInput.connect(state, send, normalizeProps) return ( <div {...api.getRootProps()}> <label {...api.getLabelProps()}>Enter number:</label> <div> <div {...api.getScrubberProps()} /> <input {...api.getInputProps()} /> </div> </div> ) }

Using the mousewheel to change value

The number input machine exposes a way to increment/decrement the value using the mouse wheel event. To activate this, pass the allowMouseWheel property to the machine's context.

const [state, send] = useMachine( numberInput.machine({ allowMouseWheel: true, }), )

Clamp value when user blurs the input

In most cases, users can type custom values in the input field. If the typed value is greater than the max, the value is reset to max when the user blur out of the input.

To disable this behavior, pass clampValueOnBlur and set to false.

const [state, send] = useMachine( numberInput.machine({ clampValueOnBlur: false, }), )

Listening for value changes

When the value changes, the onValueChange callback is invoked.

const [state, send] = useMachine( numberInput.machine({ onValueChange(details) { // details => { value: string, valueAsNumber: number } console.log("value is:", details.value) }, }), )

Usage within forms

To use the number input within forms, set the name property in the machine's context.

const [state, send] = useMachine( numberInput.machine({ name: "quantity", }), )

Adjusting the precision of the value

To format the input value to be rounded to specific decimal points, set the formatOptions and provide Intl.NumberFormatOptions such as maximumFractionDigits or minimumFractionDigits

const [state, send] = useMachine( numberInput.machine({ formatOptions: { maximumFractionDigits: 4, minimumFractionDigits: 2, }, }), )

Disabling long press spin

To disable the long press spin, set the spinOnPress to false.

const [state, send] = useMachine( numberInput.machine({ spinOnPress: false, }), )

Format and parse value

To apply custom formatting to the input's value, set the formatOptions and provide Intl.NumberFormatOptions such as style and currency

const [state, send] = useMachine( numberInput.machine({ formatOptions: { style: "currency", currency: "USD", }, }), )

Styling guide

Earlier, we mentioned that each number-input's part has a data-part attribute added to them to select and style them in the DOM.

Disabled state

When the number input is disabled, the root, label and input parts will have data-disabled attribute added to them.

The increment and decrement spin buttons are disabled when the number input is disabled and the min/max is reached.

[data-part="root"][data-disabled] { /* disabled styles for the input */ } [data-part="input"][data-disabled] { /* disabled styles for the input */ } [data-part="label"][data-disabled] { /* disabled styles for the label */ } [data-part="increment-trigger"][data-disabled] { /* disabled styles for the increment button */ } [data-part="decrement-trigger"][data-disabled] { /* disabled styles for the decrement button */ }

Invalid state

The number input is invalid, either by passing invalid: true or when the value exceeds the max and allowOverflow: true is passed. When this happens, the root, label and input parts will have data-invalid attribute added to them.

[data-part="root"][data-invalid] { /* disabled styles for the input */ } [data-part="input"][data-invalid] { /* invalid styles for the input */ } [data-part="label"][data-invalid] { /* invalid styles for the label */ }

Readonly state

When the number input is readonly, the input part will have data-readonly added.

[data-part="input"][data-readonly] { /* readonly styles for the input */ }

Increment and decrement spin buttons

The spin buttons can be styled individually with their respective data-part attribute.

[data-part="increment-trigger"] { /* styles for the increment trigger element */ } [data-part="decrement-trigger"] { /* styles for the decrement trigger element */ }

Methods and Properties

Machine Context

The number input machine exposes the following context properties:

  • idsPartial<{ root: string; label: string; input: string; incrementTrigger: string; decrementTrigger: string; scrubber: string; }>The ids of the elements in the number input. Useful for composition.
  • namestringThe name attribute of the number input. Useful for form submission.
  • formstringThe associate form of the input element.
  • disabledbooleanWhether the number input is disabled.
  • readOnlybooleanWhether the number input is readonly
  • invalidbooleanWhether the number input value is invalid.
  • requiredbooleanWhether the number input is required
  • patternstringThe pattern used to check the <input> element's value against
  • valuestringThe value of the input
  • minnumberThe minimum value of the number input
  • maxnumberThe maximum value of the number input
  • stepnumberThe amount to increment or decrement the value by
  • allowMouseWheelbooleanWhether to allow mouse wheel to change the value
  • allowOverflowbooleanWhether to allow the value overflow the min/max range
  • clampValueOnBlurbooleanWhether to clamp the value when the input loses focus (blur)
  • focusInputOnChangebooleanWhether to focus input when the value changes
  • translationsIntlTranslationsSpecifies the localized strings that identifies the accessibility elements and their states
  • formatOptionsIntl.NumberFormatOptionsThe options to pass to the `Intl.NumberFormat` constructor
  • inputModeInputModeHints at the type of data that might be entered by the user. It also determines the type of keyboard shown to the user on mobile devices
  • onValueChange(details: ValueChangeDetails) => voidFunction invoked when the value changes
  • onValueInvalid(details: ValueInvalidDetails) => voidFunction invoked when the value overflows or underflows the min/max range
  • onFocusChange(details: FocusChangeDetails) => voidFunction invoked when the number input is focused
  • spinOnPressbooleanWhether to spin the value when the increment/decrement button is pressed
  • localestringThe current locale. Based on the BCP 47 definition.
  • dir"ltr" | "rtl"The document's text/writing direction.
  • idstringThe unique identifier of the machine.
  • getRootNode() => ShadowRoot | Node | DocumentA root node to correctly resolve document in custom environments. E.x.: Iframes, Electron.

Machine API

The number input api exposes the following methods:

  • focusedbooleanWhether the input is focused.
  • invalidbooleanWhether the input is invalid.
  • emptybooleanWhether the input value is empty.
  • valuestringThe formatted value of the input.
  • valueAsNumbernumberThe value of the input as a number.
  • setValue(value: number) => voidFunction to set the value of the input.
  • clearValue() => voidFunction to clear the value of the input.
  • increment() => voidFunction to increment the value of the input by the step.
  • decrement() => voidFunction to decrement the value of the input by the step.
  • setToMax() => voidFunction to set the value of the input to the max.
  • setToMin() => voidFunction to set the value of the input to the min.
  • focus() => voidFunction to focus the input.

Data Attributes

Root
data-scope
number-input
data-part
root
data-disabled
Present when disabled
data-focus
Present when focused
data-invalid
Present when invalid
Label
data-scope
number-input
data-part
label
data-disabled
Present when disabled
data-focus
Present when focused
data-invalid
Present when invalid
Control
data-scope
number-input
data-part
control
data-focus
Present when focused
data-disabled
Present when disabled
data-invalid
Present when invalid
ValueText
data-scope
number-input
data-part
value-text
data-disabled
Present when disabled
data-invalid
Present when invalid
data-focus
Present when focused
Input
data-scope
number-input
data-part
input
data-invalid
Present when invalid
data-disabled
Present when disabled
DecrementTrigger
data-scope
number-input
data-part
decrement-trigger
data-disabled
Present when disabled
IncrementTrigger
data-scope
number-input
data-part
increment-trigger
data-disabled
Present when disabled
Scrubber
data-scope
number-input
data-part
scrubber
data-disabled
Present when disabled

Accessibility

Keyboard Interactions

  • ArrowUp
    Increments the value of the number input by a predefined step.
  • ArrowDown
    Decrements the value of the number input by a predefined step.
  • PageUp
    Increments the value of the number input by a larger predefined step.
  • PageDown
    Decrements the value of the number input by a larger predefined step.
  • Home
    Sets the value of the number input to its minimum allowed value.
  • End
    Sets the value of the number input to its maximum allowed value.
  • Enter
    Submits the value entered in the number input.

Edit this page on GitHub

Proudly made in🇳🇬by Segun Adebayo

Copyright © 2024
On this page