Number Input
A field that allows user input of numeric values.
You can explore the number-input component in the following curated examples.
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.
Examples
Learn how to use the NumberInput
component in your project. Let's take a look at the most basic
example:
import { NumberInput } from '@ark-ui/react/number-input'
export const Basic = () => (
<NumberInput.Root>
<NumberInput.Label>Label</NumberInput.Label>
<NumberInput.Input />
<NumberInput.Control>
<NumberInput.DecrementTrigger>-1</NumberInput.DecrementTrigger>
<NumberInput.IncrementTrigger>+1</NumberInput.IncrementTrigger>
</NumberInput.Control>
</NumberInput.Root>
)
import { NumberInput } from '@ark-ui/solid/number-input'
export const Basic = () => (
<NumberInput.Root>
<NumberInput.Label>Label</NumberInput.Label>
<NumberInput.Input />
<NumberInput.Control>
<NumberInput.DecrementTrigger>-1</NumberInput.DecrementTrigger>
<NumberInput.IncrementTrigger>+1</NumberInput.IncrementTrigger>
</NumberInput.Control>
</NumberInput.Root>
)
<script setup lang="ts">
import { NumberInput } from '@ark-ui/vue/number-input'
</script>
<template>
<NumberInput.Root>
<NumberInput.Label>Label</NumberInput.Label>
<NumberInput.Input />
<NumberInput.Control>
<NumberInput.DecrementTrigger>-1</NumberInput.DecrementTrigger>
<NumberInput.IncrementTrigger>+1</NumberInput.IncrementTrigger>
</NumberInput.Control>
</NumberInput.Root>
</template>
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.
import { NumberInput } from '@ark-ui/react/number-input'
export const MinMax = () => (
<NumberInput.Root min={0} max={10}>
<NumberInput.Scrubber />
<NumberInput.Label>Label</NumberInput.Label>
<NumberInput.Input />
<NumberInput.Control>
<NumberInput.DecrementTrigger>-1</NumberInput.DecrementTrigger>
<NumberInput.IncrementTrigger>+1</NumberInput.IncrementTrigger>
</NumberInput.Control>
</NumberInput.Root>
)
import { NumberInput } from '@ark-ui/solid/number-input'
export const MinMax = () => (
<NumberInput.Root min={0} max={10}>
<NumberInput.Scrubber />
<NumberInput.Label>Label</NumberInput.Label>
<NumberInput.Input />
<NumberInput.Control>
<NumberInput.DecrementTrigger>-1</NumberInput.DecrementTrigger>
<NumberInput.IncrementTrigger>+1</NumberInput.IncrementTrigger>
</NumberInput.Control>
</NumberInput.Root>
)
<script setup lang="ts">
import { NumberInput } from '@ark-ui/vue/number-input'
</script>
<template>
<NumberInput.Root :min="0" :max="10">
<NumberInput.Scrubber />
<NumberInput.Label>Label</NumberInput.Label>
<NumberInput.Input />
<NumberInput.Control>
<NumberInput.DecrementTrigger>-1</NumberInput.DecrementTrigger>
<NumberInput.IncrementTrigger>+1</NumberInput.IncrementTrigger>
</NumberInput.Control>
</NumberInput.Root>
</template>
Adjusting the precision of the value
In some cases, you might need the value to be rounded to specific decimal points. Set the
formatOptions
and provide Intl.NumberFormatOptions
such as maximumFractionDigits
or
minimumFractionDigits
.
import { NumberInput } from '@ark-ui/react/number-input'
export const FractionDigits = () => (
<NumberInput.Root
formatOptions={{ minimumFractionDigits: 2, maximumFractionDigits: 3 }}
defaultValue="1.00"
>
<NumberInput.Scrubber />
<NumberInput.Label>Label</NumberInput.Label>
<NumberInput.Input />
<NumberInput.Control>
<NumberInput.DecrementTrigger>-1</NumberInput.DecrementTrigger>
<NumberInput.IncrementTrigger>+1</NumberInput.IncrementTrigger>
</NumberInput.Control>
</NumberInput.Root>
)
import { NumberInput } from '@ark-ui/solid/number-input'
export const FractionDigits = () => (
<NumberInput.Root
formatOptions={{ minimumFractionDigits: 2, maximumFractionDigits: 4 }}
value="1.00"
>
<NumberInput.Scrubber />
<NumberInput.Label>Label</NumberInput.Label>
<NumberInput.Input />
<NumberInput.Control>
<NumberInput.DecrementTrigger>-1</NumberInput.DecrementTrigger>
<NumberInput.IncrementTrigger>+1</NumberInput.IncrementTrigger>
</NumberInput.Control>
</NumberInput.Root>
)
<script setup lang="ts">
import { NumberInput } from '@ark-ui/vue/number-input'
</script>
<template>
<NumberInput.Root
:formatOptions="{ minimumFractionDigits: 2, maximumFractionDigits: 3 }"
model-value="1.00"
>
<NumberInput.Scrubber />
<NumberInput.Label>Label</NumberInput.Label>
<NumberInput.Input />
<NumberInput.Control>
<NumberInput.DecrementTrigger>-1</NumberInput.DecrementTrigger>
<NumberInput.IncrementTrigger>+1</NumberInput.IncrementTrigger>
</NumberInput.Control>
</NumberInput.Root>
</template>
Scrubbing the input value
The NumberInput supports the scrubber interaction pattern. To use this pattern, render the
NumberInput.Scrubber
component. It uses the Pointer lock API and tracks the pointer movement. It
also renders a virtual cursor which mimics the real cursor's pointer.
import { NumberInput } from '@ark-ui/react/number-input'
export const Scrubber = () => (
<NumberInput.Root>
<NumberInput.Scrubber />
<NumberInput.Label>Label</NumberInput.Label>
<NumberInput.Input />
<NumberInput.Control>
<NumberInput.DecrementTrigger>-1</NumberInput.DecrementTrigger>
<NumberInput.IncrementTrigger>+1</NumberInput.IncrementTrigger>
</NumberInput.Control>
</NumberInput.Root>
)
import { NumberInput } from '@ark-ui/solid/number-input'
export const Scrubber = () => (
<NumberInput.Root>
<NumberInput.Scrubber />
<NumberInput.Label>Label</NumberInput.Label>
<NumberInput.Input />
<NumberInput.Control>
<NumberInput.DecrementTrigger>-1</NumberInput.DecrementTrigger>
<NumberInput.IncrementTrigger>+1</NumberInput.IncrementTrigger>
</NumberInput.Control>
</NumberInput.Root>
)
<script setup lang="ts">
import { NumberInput } from '@ark-ui/vue/number-input'
</script>
<template>
<NumberInput.Root>
<NumberInput.Scrubber />
<NumberInput.Label>Label</NumberInput.Label>
<NumberInput.Input />
<NumberInput.Control>
<NumberInput.DecrementTrigger>-1</NumberInput.DecrementTrigger>
<NumberInput.IncrementTrigger>+1</NumberInput.IncrementTrigger>
</NumberInput.Control>
</NumberInput.Root>
</template>
Using the mouse wheel to change value
The NumberInput exposes a way to increment/decrement the value using the mouse wheel event. To
activate this, set the allowMouseWheel
prop to true
.
import { NumberInput } from '@ark-ui/react/number-input'
export const MouseWheel = () => (
<NumberInput.Root allowMouseWheel>
<NumberInput.Scrubber />
<NumberInput.Label>Label</NumberInput.Label>
<NumberInput.Input />
<NumberInput.Control>
<NumberInput.DecrementTrigger>-1</NumberInput.DecrementTrigger>
<NumberInput.IncrementTrigger>+1</NumberInput.IncrementTrigger>
</NumberInput.Control>
</NumberInput.Root>
)
import { NumberInput } from '@ark-ui/solid/number-input'
export const MouseWheel = () => (
<NumberInput.Root allowMouseWheel>
<NumberInput.Scrubber />
<NumberInput.Label>Label</NumberInput.Label>
<NumberInput.Input />
<NumberInput.Control>
<NumberInput.DecrementTrigger>-1</NumberInput.DecrementTrigger>
<NumberInput.IncrementTrigger>+1</NumberInput.IncrementTrigger>
</NumberInput.Control>
</NumberInput.Root>
)
<script setup lang="ts">
import { NumberInput } from '@ark-ui/vue/number-input'
</script>
<template>
<NumberInput.Root allowMouseWheel>
<NumberInput.Scrubber />
<NumberInput.Label>Label</NumberInput.Label>
<NumberInput.Input />
<NumberInput.Control>
<NumberInput.DecrementTrigger>-1</NumberInput.DecrementTrigger>
<NumberInput.IncrementTrigger>+1</NumberInput.IncrementTrigger>
</NumberInput.Control>
</NumberInput.Root>
</template>
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
.
import { NumberInput } from '@ark-ui/react/number-input'
export const NoClamp = () => (
<NumberInput.Root clampValueOnBlur={false}>
<NumberInput.Scrubber />
<NumberInput.Label>Label</NumberInput.Label>
<NumberInput.Input />
<NumberInput.Control>
<NumberInput.DecrementTrigger>-1</NumberInput.DecrementTrigger>
<NumberInput.IncrementTrigger>+1</NumberInput.IncrementTrigger>
</NumberInput.Control>
</NumberInput.Root>
)
import { NumberInput } from '@ark-ui/solid/number-input'
export const NoClamp = () => (
<NumberInput.Root clampValueOnBlur={false}>
<NumberInput.Scrubber />
<NumberInput.Label>Label</NumberInput.Label>
<NumberInput.Input />
<NumberInput.Control>
<NumberInput.DecrementTrigger>-1</NumberInput.DecrementTrigger>
<NumberInput.IncrementTrigger>+1</NumberInput.IncrementTrigger>
</NumberInput.Control>
</NumberInput.Root>
)
<script setup lang="ts">
import { NumberInput } from '@ark-ui/vue/number-input'
</script>
<template>
<NumberInput.Root :clampValueOnBlur="false">
<NumberInput.Scrubber />
<NumberInput.Label>Label</NumberInput.Label>
<NumberInput.Input />
<NumberInput.Control>
<NumberInput.DecrementTrigger>-1</NumberInput.DecrementTrigger>
<NumberInput.IncrementTrigger>+1</NumberInput.IncrementTrigger>
</NumberInput.Control>
</NumberInput.Root>
</template>
Usage within forms
To use the number input within forms, set the name
prop.
import { NumberInput } from '@ark-ui/react/number-input'
export const FormUsage = () => (
<NumberInput.Root name="quantity">
<NumberInput.Scrubber />
<NumberInput.Label>Label</NumberInput.Label>
<NumberInput.Input />
<NumberInput.Control>
<NumberInput.DecrementTrigger>-1</NumberInput.DecrementTrigger>
<NumberInput.IncrementTrigger>+1</NumberInput.IncrementTrigger>
</NumberInput.Control>
</NumberInput.Root>
)
import { NumberInput } from '@ark-ui/solid/number-input'
export const FormUsage = () => (
<NumberInput.Root name="quantity">
<NumberInput.Scrubber />
<NumberInput.Label>Label</NumberInput.Label>
<NumberInput.Input />
<NumberInput.Control>
<NumberInput.DecrementTrigger>-1</NumberInput.DecrementTrigger>
<NumberInput.IncrementTrigger>+1</NumberInput.IncrementTrigger>
</NumberInput.Control>
</NumberInput.Root>
)
<script setup lang="ts">
import { NumberInput } from '@ark-ui/vue/number-input'
</script>
<template>
<NumberInput.Root name="quantity">
<NumberInput.Scrubber />
<NumberInput.Label>Label</NumberInput.Label>
<NumberInput.Input />
<NumberInput.Control>
<NumberInput.DecrementTrigger>-1</NumberInput.DecrementTrigger>
<NumberInput.IncrementTrigger>+1</NumberInput.IncrementTrigger>
</NumberInput.Control>
</NumberInput.Root>
</template>
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
.
import { NumberInput } from '@ark-ui/react/number-input'
export const Formatted = () => (
<NumberInput.Root
formatOptions={{
style: 'currency',
currency: 'USD',
}}
>
<NumberInput.Scrubber />
<NumberInput.Label>Label</NumberInput.Label>
<NumberInput.Input />
<NumberInput.Control>
<NumberInput.DecrementTrigger>-1</NumberInput.DecrementTrigger>
<NumberInput.IncrementTrigger>+1</NumberInput.IncrementTrigger>
</NumberInput.Control>
</NumberInput.Root>
)
import { NumberInput } from '@ark-ui/solid/number-input'
export const Formatted = () => (
<NumberInput.Root
formatOptions={{
style: 'currency',
currency: 'USD',
}}
>
<NumberInput.Scrubber />
<NumberInput.Label>Label</NumberInput.Label>
<NumberInput.Input />
<NumberInput.Control>
<NumberInput.DecrementTrigger>-1</NumberInput.DecrementTrigger>
<NumberInput.IncrementTrigger>+1</NumberInput.IncrementTrigger>
</NumberInput.Control>
</NumberInput.Root>
)
<script setup lang="ts">
import { NumberInput } from '@ark-ui/vue/number-input'
</script>
<template>
<NumberInput.Root :formatOptions="{ style: 'currency', currency: 'USD' }">
<NumberInput.Scrubber />
<NumberInput.Label>Label</NumberInput.Label>
<NumberInput.Input />
<NumberInput.Control>
<NumberInput.DecrementTrigger>-1</NumberInput.DecrementTrigger>
<NumberInput.IncrementTrigger>+1</NumberInput.IncrementTrigger>
</NumberInput.Control>
</NumberInput.Root>
</template>
Using the Field Component
The Field
component helps manage form-related state and accessibility attributes of a number input.
It includes handling ARIA labels, helper text, and error text to ensure proper accessibility.
import { Field } from '@ark-ui/react/field'
import { NumberInput } from '@ark-ui/react/number-input'
export const WithField = (props: Field.RootProps) => (
<Field.Root {...props}>
<NumberInput.Root>
<NumberInput.Label>Label</NumberInput.Label>
<NumberInput.Input />
<NumberInput.Control>
<NumberInput.DecrementTrigger>-1</NumberInput.DecrementTrigger>
<NumberInput.IncrementTrigger>+1</NumberInput.IncrementTrigger>
</NumberInput.Control>
</NumberInput.Root>
<Field.HelperText>Additional Info</Field.HelperText>
<Field.ErrorText>Error Info</Field.ErrorText>
</Field.Root>
)
import { Field } from '@ark-ui/solid/field'
import { NumberInput } from '@ark-ui/solid/number-input'
export const WithField = (props: Field.RootProps) => (
<Field.Root {...props}>
<NumberInput.Root>
<NumberInput.Label>Label</NumberInput.Label>
<NumberInput.Input />
<NumberInput.Control>
<NumberInput.DecrementTrigger>-1</NumberInput.DecrementTrigger>
<NumberInput.IncrementTrigger>+1</NumberInput.IncrementTrigger>
</NumberInput.Control>
</NumberInput.Root>
<Field.HelperText>Additional Info</Field.HelperText>
<Field.ErrorText>Error Info</Field.ErrorText>
</Field.Root>
)
<script setup lang="ts">
import { Field } from '@ark-ui/vue/field'
import { NumberInput } from '@ark-ui/vue/number-input'
</script>
<template>
<Field.Root>
<NumberInput.Root>
<NumberInput.Label>Label</NumberInput.Label>
<NumberInput.Input />
<NumberInput.Control>
<NumberInput.DecrementTrigger>-1</NumberInput.DecrementTrigger>
<NumberInput.IncrementTrigger>+1</NumberInput.IncrementTrigger>
</NumberInput.Control>
</NumberInput.Root>
<Field.HelperText>Additional Info</Field.HelperText>
<Field.ErrorText>Error Info</Field.ErrorText>
</Field.Root>
</template>
Using the Root Provider
The RootProvider
component provides a context for the number-input. It accepts the value of the useNumber-input
hook.
You can leverage it to access the component state and methods from outside the number-input.
import { NumberInput, useNumberInput } from '@ark-ui/react/number-input'
export const RootProvider = () => {
const numberInput = useNumberInput()
return (
<>
<button onClick={() => numberInput.focus()}>Focus</button>
<NumberInput.RootProvider value={numberInput}>
<NumberInput.Label>Label</NumberInput.Label>
<NumberInput.Input />
<NumberInput.Control>
<NumberInput.DecrementTrigger>-1</NumberInput.DecrementTrigger>
<NumberInput.IncrementTrigger>+1</NumberInput.IncrementTrigger>
</NumberInput.Control>
</NumberInput.RootProvider>
</>
)
}
import { NumberInput, useNumberInput } from '@ark-ui/solid/number-input'
export const RootProvider = () => {
const numberInput = useNumberInput()
return (
<>
<button onClick={() => numberInput().focus()}>Focus</button>
<NumberInput.RootProvider value={numberInput}>
<NumberInput.Label>Label</NumberInput.Label>
<NumberInput.Input />
<NumberInput.Control>
<NumberInput.DecrementTrigger>-1</NumberInput.DecrementTrigger>
<NumberInput.IncrementTrigger>+1</NumberInput.IncrementTrigger>
</NumberInput.Control>
</NumberInput.RootProvider>
</>
)
}
<script setup lang="ts">
import { NumberInput, useNumberInput } from '@ark-ui/vue/number-input'
const numberInput = useNumberInput()
</script>
<template>
<button @click="numberInput.focus()">Focus</button>
<NumberInput.RootProvider :value="numberInput">
<NumberInput.Label>Label</NumberInput.Label>
<NumberInput.Input />
<NumberInput.Control>
<NumberInput.DecrementTrigger>-1</NumberInput.DecrementTrigger>
<NumberInput.IncrementTrigger>+1</NumberInput.IncrementTrigger>
</NumberInput.Control>
</NumberInput.RootProvider>
</template>
If you're using the
RootProvider
component, you don't need to use theRoot
component.
API Reference
Root
Prop | Default | Type |
---|---|---|
allowMouseWheel | boolean Whether to allow mouse wheel to change the value | |
allowOverflow | true | boolean Whether to allow the value overflow the min/max range |
asChild | boolean Use the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. | |
clampValueOnBlur | true | boolean Whether to clamp the value when the input loses focus (blur) |
defaultValue | string The initial value of the number input when it is first rendered. Use when you do not need to control the state of the number input. | |
disabled | boolean Whether the number input is disabled. | |
focusInputOnChange | true | boolean Whether to focus input when the value changes |
form | string The associate form of the input element. | |
formatOptions | NumberFormatOptions The options to pass to the `Intl.NumberFormat` constructor | |
id | string The unique identifier of the machine. | |
ids | Partial<{
root: string
label: string
input: string
incrementTrigger: string
decrementTrigger: string
scrubber: string
}> The ids of the elements in the number input. Useful for composition. | |
inputMode | 'decimal' | InputMode Hints 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 |
invalid | boolean Whether the number input value is invalid. | |
locale | 'en-US' | string The current locale. Based on the BCP 47 definition. |
max | Number.MAX_SAFE_INTEGER | number The maximum value of the number input |
min | Number.MIN_SAFE_INTEGER | number The minimum value of the number input |
name | string The name attribute of the number input. Useful for form submission. | |
onFocusChange | (details: FocusChangeDetails) => void Function invoked when the number input is focused | |
onValueChange | (details: ValueChangeDetails) => void Function invoked when the value changes | |
onValueInvalid | (details: ValueInvalidDetails) => void Function invoked when the value overflows or underflows the min/max range | |
pattern | '[0-9]*(.[0-9]+)?' | string The pattern used to check the <input> element's value against |
readOnly | boolean Whether the number input is readonly | |
required | boolean Whether the number input is required | |
spinOnPress | true | boolean Whether to spin the value when the increment/decrement button is pressed |
step | 1 | number The amount to increment or decrement the value by |
translations | IntlTranslations Specifies the localized strings that identifies the accessibility elements and their states | |
value | string The value of the input |
Data Attribute | Value |
---|---|
[data-scope] | number-input |
[data-part] | root |
[data-disabled] | Present when disabled |
[data-focus] | Present when focused |
[data-invalid] | Present when invalid |
Control
Prop | Default | Type |
---|---|---|
asChild | boolean Use the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. |
Data Attribute | Value |
---|---|
[data-scope] | number-input |
[data-part] | control |
[data-focus] | Present when focused |
[data-disabled] | Present when disabled |
[data-invalid] | Present when invalid |
DecrementTrigger
Prop | Default | Type |
---|---|---|
asChild | boolean Use the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. |
Data Attribute | Value |
---|---|
[data-scope] | number-input |
[data-part] | decrement-trigger |
[data-disabled] | Present when disabled |
IncrementTrigger
Prop | Default | Type |
---|---|---|
asChild | boolean Use the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. |
Data Attribute | Value |
---|---|
[data-scope] | number-input |
[data-part] | increment-trigger |
[data-disabled] | Present when disabled |
Input
Prop | Default | Type |
---|---|---|
asChild | boolean Use the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. |
Data Attribute | Value |
---|---|
[data-scope] | number-input |
[data-part] | input |
[data-invalid] | Present when invalid |
[data-disabled] | Present when disabled |
Label
Prop | Default | Type |
---|---|---|
asChild | boolean Use the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. |
Data Attribute | Value |
---|---|
[data-scope] | number-input |
[data-part] | label |
[data-disabled] | Present when disabled |
[data-focus] | Present when focused |
[data-invalid] | Present when invalid |
RootProvider
Prop | Default | Type |
---|---|---|
value | UseNumberInputReturn | |
asChild | boolean Use the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. |
Scrubber
Prop | Default | Type |
---|---|---|
asChild | boolean Use the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. |
Data Attribute | Value |
---|---|
[data-scope] | number-input |
[data-part] | scrubber |
[data-disabled] | Present when disabled |
ValueText
Prop | Default | Type |
---|---|---|
asChild | boolean Use the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. |
Data Attribute | Value |
---|---|
[data-scope] | number-input |
[data-part] | value-text |
[data-disabled] | Present when disabled |
[data-invalid] | Present when invalid |
[data-focus] | Present when focused |
Accessibility
Complies with the Spinbutton WAI-ARIA design pattern.
Keyboard Support
Key | Description |
---|---|
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. |