Skip to main content
Vuetify0 v1.0 is here
Vuetify0 Logo
Theme
Mode
Palettes
Accessibility
Vuetify One
Sign in to Vuetify One

Access premium tools across the Vuetify ecosystem — Bin, Play, Studio, and more.

Not a subscriber? See what's included

NumberField

Numeric input with increment/decrement buttons, drag-to-scrub, and locale-aware formatting. Supports currency, percent, and unit display via Intl.NumberFormat.

Edit this page
Report a Bug
Open issues
View on GitHub
Copy Markdown

PreviewRenders elementIntermediateJul 19, 2026

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">
        &minus;
      </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

vue
<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.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.

NumberField Architecture

Use controls to zoom and pan. Click outside or press Escape to close.

NumberField Architecture

Examples

Cart Quantity Editor

A shopping-cart editor where every line item is a quantity NumberField that drives a live order summary. Each field is bound with :min="0", :max="item.stock", :step="1", and clamp, so a shopper can never order more than what’s in stock and typing an out-of-range value snaps back to the boundary on blur. The out-of-stock line has a stock of zero, so the component sets :disabled on its NumberField.Root — the increment, decrement, and control all read the Root’s data-disabled and dim accordingly.

The interesting part is how the totals stay in sync. Quantities live as numbers on the cart items in the composable, and subtotal, tax, and total are computed reductions over those quantities — touch any stepper and every dependent figure recomputes. Each NumberField.Root carries a name (qty-{id}), which lands directly on the underlying NumberField.Control input, so the quantities post with native form submission — NumberField has no separate hidden-input sub-component. The whole thing is wrapped in a Form, whose @submit is pass-through, so the handler guards on payload.valid before committing the order — and because total can be zero, the submit button disables itself rather than placing an empty order.

Reach for this triad shape whenever numeric inputs feed a derived calculation: keep the line data and the computed totals in a use* composable, render the compound surface plus its UnoCSS styling in a reusable component, and let a thin entry wire them together and swap in a confirmation panel. For the underlying numeric math and formatting primitive, see createNumberField; for the field’s keyboard and ARIA contract, see the Accessibility section below.

FileRole
useCart.tsComposable — cart line items, computed subtotal/tax/total, checkout/reset logic
CartLineItems.vueReusable component — renders the Form with a quantity NumberField per line and the totals summary
cart-line-items.vueEntry — wires the composable to the editor and swaps in an order-confirmation panel on submit
  • Organic cotton tee

    $24.00 each · 8 in stock

    $48.00

  • Stoneware mug

    $16.50 each · 12 in stock

    $16.50

  • Canvas tote bag

    $12.00 each · 4 in stock

    $36.00

  • Embroidered cap

    $28.00 each · Out of stock

    $0.00

Subtotal
$100.50
Tax (8.25%)
$8.29
Total
$108.79

6 items in cart. Each quantity clamps to its line's stock — the out-of-stock cap is disabled at zero.

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):

vue
<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:

vue
<template>
  <NumberField.Root v-model="value" wheel>
    <NumberField.Control />
  </NumberField.Root>
</template>

Data Attributes

Style interactive states without slot props:

AttributeValuesComponents
data-statevalid, invalid, pristineRoot, Control
data-dirtytrueRoot
data-focusedtrueRoot, Control
data-disabledtrueRoot, Control, Increment, Decrement, Scrub
data-readonlytrueRoot, Control, Scrub

Accessibility

NumberField.Control renders with role="spinbutton" and full ARIA attributes per the WAI-ARIA Spinbutton pattern↗︎.

ARIA Attributes

AttributeValueNotes
rolespinbuttonApplied to Control
aria-valuenowCurrent valueundefined when empty
aria-valueminMin valueOnly when finite
aria-valuemaxMax valueOnly when finite
aria-valuetextFormatted stringScreen readers announce “$42.00” not “42”
aria-invalidtrueWhen validation fails
aria-labelLabel textFrom Root’s label prop
aria-describedbyDescription IDWhen Description is mounted
aria-errormessageError IDWhen Error is mounted with messages
aria-requiredtrueWhen 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

KeyAction
ArrowUpIncrement by one step
ArrowDownDecrement by one step
Shift+ArrowUpIncrement by 10 steps
Shift+ArrowDownDecrement by 10 steps
PageUpIncrement by leap (default step × 10)
PageDownDecrement by leap (default step × 10)
HomeSet to minimum
EndSet to maximum
EnterCommit the typed value

FAQ

Discord
Need help? Join our community for support and discussions ↗
Was this page helpful?

© 2016-1970 Vuetify, LLC
Services
Ctrl+/