Number field

Enter a bounded number with clear increment controls.

pnpm dlx shadcn@latest add @uiarc/number-field
Live · keyboard ready
  • Bounded quantities like seats, items, or prices where the exact number matters.
  • Values people nudge with steppers, arrow keys, or by dragging the label with scrub.
  • Numbers with units, using a prefix or a pluralizing suffix function.
  • Use slider when the approximate position matters more than the exact number.
  • Use input for numeric strings like phone numbers or codes.

Installation

Add Number field with the shadcn CLI, or copy the source by hand.

pnpm dlx shadcn@latest add @uiarc/number-field

Adds the component and its local dependencies, and installs motion, lucide-react. First time? Add the @uiarc registry to components.json, or use the full URL:

example.tsx
import { NumberField } from "@/registry/components/number-field/number-field"; export function SeatsField() {  const [seats, setSeats] = useState(5);  return (    <NumberField      label="Seats"      value={seats}      onValueChange={setSeats}      min={1}      max={500}      suffix={(n) => (n === 1 ? " seat" : " seats")}      scrub    />  );}

NumberField

A numeric spinbutton with stepper buttons, rolling digits, hold-to-repeat, and optional label scrubbing.

PropTypeDefaultDescription
labelRequiredstring–Visible label; also names the stepper buttons.
valuenumber–Controlled value.
defaultValuenumber0Initial value when uncontrolled.
onValueChange(value: number) => void–Called on every committed step, scrub, or valid typed draft.
minnumber0Lower bound.
maxnumberNumber.MAX_SAFE_INTEGERUpper bound.
stepnumber1Increment; its precision also sets default fraction digits.
largeStepnumberstep * 10Distance for PageUp, PageDown, and Shift with an arrow.
descriptionstring–Helper copy under the field.
disabledboolean–Disables the input and buttons.
idstring–Input id. Generated when omitted.
prefixstring | ((value: number) => string)–Text before the number, such as "$".
suffixstring | ((value: number) => string)–Text after the number; a function can pluralize, e.g. n => n === 1 ? " seat" : " seats".
scrubbooleanfalseDrag the label sideways to change the value.
localestring"en-US"Formatting locale, fixed so server and client match.
formatOptions{ minimumFractionDigits?: number; maximumFractionDigits?: number; useGrouping?: boolean }–Fraction digits and grouping.
size"sm" | "md" | "lg""md"Control height, type size, and default width (164, 196, or 228px).
limitHintboolean | ((edge: "min" | "max", limit: number) => string)trueNote beside the label when a press meets a limit (about 1.5s) or a typed value passes one. Defaults to "Max 10 seats" using the prefix and suffix; false hides it.
ArrowUporArrowDown
Steps by step; holding repeats and speeds up, and keeps pushing at a limit.
Shift+ArroworPageUporPageDown
Steps by largeStep.
HomeorEnd
Jumps to min or max when not typing.
Enter
Commits a typed draft, clamped to min and max, or selects the value.
Escape
Reverts a typed draft to the value before editing.
  • The input has role="spinbutton" with aria-valuenow, aria-valuemin, aria-valuemax, and aria-valuetext including prefix and suffix.
  • Stepper buttons are labelled "Increase <label>" and "Decrease <label>" and marked aria-disabled at a limit.
  • Changes are announced through a polite aria-live region, and a clamp says which limit it met ("10 seats, maximum"); rolling digits are aria-hidden.
  • A typed value past a limit sets aria-invalid and adds the limit note to aria-describedby until it commits. The warning also uses copy, never color alone.
  • No focus rings: focus darkens the shell border, and a focused stepper button takes its hover fill.
  • Digits roll on wheels in the direction of change (up rolls up, down rolls down) with tabular numerals, and affixes reword in place.
  • Holding a stepper repeats after 400ms and ramps from 150ms to 40ms per step.
  • At a limit the value strains a few pixels toward the press and springs home; pushes in a row strain a little further, capped like overscroll. The refused button shakes once and the limit note rises in beside the label.
  • A typed value past a limit tints the shell with the warning role; on Enter or blur it springs back to the limit with a digit roll.
  • Reduced motion changes the value at once and answers a press at a limit with a brief warning tint on the number instead of the strain and shake.
  • The control is min(100%, 196px) wide at md (164px at sm, 228px at lg), overridable with --number-field-width. The label row, with its limit note, follows the same width.
  • Stepper buttons are 28, 34, or 40px by size with touch-action manipulation, so fast taps do not zoom.
  • Scrubbing the label uses pointer capture with touch-action pan-y, so vertical scrolling still works on touch.
  • Holding a stepper repeats on timeouts that speed up, not a per-frame loop.
  • Two ResizeObservers handle digit layout and the helper row; fine for forms, not for large grids.

Notes for AI

Give your coding assistant the Markdown reference instead of screenshots.

  • Use for bounded integers or decimals like quantities, seats, and prices. Use slider when the approximate position matters more than the exact number.
  • Controlled with value and onValueChange or uncontrolled with defaultValue. There is no name prop; add a hidden input for plain forms.
  • Set min below zero to allow negatives; the input then accepts a minus sign.
  • The limit note sits at the end of the label row, so keep labels short enough to share the control's width with it, or set limitHint={false}.

The full library index for assistants is at /llms.txt.