NumberField
Numeric input with increment/decrement buttons, drag-to-scrub, and locale-aware formatting. Supports currency, percent, and unit display via Intl.NumberFormat.
Usage
NumberField renders a spinbutton input with optional increment, decrement, and scrub controls. Wire it up with v-model for two-way binding.
<script setup lang="ts">
import { NumberField } from '@vuetify/v0'
import { shallowRef } from 'vue'
const quantity = shallowRef<number | null>(1)
</script>
<template>
<div class="flex items-center justify-center">
<NumberField.Root v-model="quantity" :max="99" :min="0">
<NumberField.Decrement class="px-3 py-2 border border-divider rounded-l-lg hover:bg-surface-tint disabled:opacity-50">
−
</NumberField.Decrement>
<NumberField.Control class="w-20 text-center border-y border-divider py-2 outline-none bg-transparent" />
<NumberField.Increment class="px-3 py-2 border border-divider rounded-r-lg hover:bg-surface-tint disabled:opacity-50">
+
</NumberField.Increment>
</NumberField.Root>
</div>
</template>
Anatomy
<script setup lang="ts">
import { NumberField } from '@vuetify/v0'
</script>
<template>
<NumberField.Root>
<NumberField.Scrub />
<NumberField.Description />
<NumberField.Decrement />
<NumberField.Control />
<NumberField.Increment />
<NumberField.Error />
<NumberField.HiddenInput />
</NumberField.Root>
</template>Architecture
Root composes createNumberField which delegates to createInput for field state and createNumeric for math operations. Each sub-component consumes the root context.
Examples
Currency Formatting
Locale-aware currency display using Intl.NumberFormat. The format prop accepts any Intl.NumberFormatOptions, and the Scrub label allows drag-to-adjust the value.
Total: $48.30
Design Tool Scrub
Figma-style property inputs where the label acts as a scrub control. Drag horizontally on any label to adjust its value. Uses the Pointer Lock API for unbounded movement.
Recipes
Spin-on-Hold
Increment and Decrement buttons repeat automatically when held. Configure timing with spin-delay (initial pause, default 400ms) and spin-rate (repeat interval, default 60ms):
<template>
<NumberField.Root :spin-delay="300" :spin-rate="40">
<NumberField.Decrement>-</NumberField.Decrement>
<NumberField.Control />
<NumberField.Increment>+</NumberField.Increment>
</NumberField.Root>
</template>Mouse Wheel
Enable value adjustment via scroll wheel when the input is focused:
<template>
<NumberField.Root v-model="value" wheel>
<NumberField.Control />
</NumberField.Root>
</template>Data Attributes
Style interactive states without slot props:
| Attribute | Values | Components |
|---|---|---|
data-state | valid, invalid, pristine | Root, Control |
data-dirty | true | Root |
data-focused | true | Root, Control |
data-disabled | true | Root, Control, Increment, Decrement, Scrub |
data-readonly | true | Root, Control, Scrub |
Accessibility
NumberField.Control renders with role="spinbutton" and full ARIA attributes per the WAI-ARIA Spinbutton pattern↗.
ARIA Attributes
| Attribute | Value | Notes |
|---|---|---|
role | spinbutton | Applied to Control |
aria-valuenow | Current value | undefined when empty |
aria-valuemin | Min value | Only when finite |
aria-valuemax | Max value | Only when finite |
aria-valuetext | Formatted string | Screen readers announce “$42.00” not “42” |
aria-invalid | true | When validation fails |
aria-label | Label text | From Root’s label prop |
aria-describedby | Description ID | When Description is mounted |
aria-errormessage | Error ID | When Error is mounted with messages |
aria-required | true | When Root has required |
Increment and Decrement buttons use tabindex="-1" to keep them out of the tab sequence — only the Input is focusable.
Keyboard Navigation
| Key | Action |
|---|---|
ArrowUp | Increment by one step |
ArrowDown | Decrement by one step |
Shift+ArrowUp | Increment by 10 steps |
Shift+ArrowDown | Decrement by 10 steps |
PageUp | Increment by leap (default step × 10) |
PageDown | Decrement by leap (default step × 10) |
Home | Set to minimum |
End | Set to maximum |
Enter | Commit the typed value |
Root accepts a locale prop (BCP 47 tag, defaults to en-US) and a format prop with Intl.NumberFormatOptions. While the input is focused it shows the raw number for editing; on blur it displays the formatted string. Parsing strips locale-specific group separators and currency symbols automatically.
On blur, the Input parses the text via parse(). If the result is NaN, the value becomes null. If clamp is true (default), the value is clamped to min/max and snapped to the nearest step.
Scrub accepts a sensitivity prop (default 1) that controls how many pixels of horizontal movement equal one step. Higher values require more movement per step for finer control.
Yes. Only Root and Input are required. Buttons, Scrub, Description, and Error are all optional.
NumberField.Root
Props
Events
update:model-value
[value: number | null]Slots
default
NumberFieldRootSlotPropsNumberField.Control
Props
namespace
string | undefinedNamespace for connecting to parent NumberField.Root
Default: "v0:number-field:root"
Slots
default
NumberFieldControlSlotPropsNumberField.Decrement
Props
namespace
string | undefinedNamespace for connecting to parent NumberField.Root
Default: "v0:number-field:root"
Slots
default
NumberFieldDecrementSlotPropsNumberField.Description
Props
namespace
string | undefinedNamespace for connecting to parent NumberField.Root
Default: "v0:number-field:root"
Slots
default
NumberFieldDescriptionSlotPropsNumberField.Error
Props
namespace
string | undefinedNamespace for connecting to parent NumberField.Root
Default: "v0:number-field:root"
Slots
default
NumberFieldErrorSlotPropsNumberField.Increment
Props
namespace
string | undefinedNamespace for connecting to parent NumberField.Root
Default: "v0:number-field:root"
Slots
default
NumberFieldIncrementSlotPropsNumberField.Scrub
Props
namespace
string | undefinedNamespace for connecting to parent NumberField.Root
Default: "v0:number-field:root"
Slots
default
NumberFieldScrubSlotProps