motif svelteArc

Number field ported

A bounded number with odometer digits and steppers.

A bounded number with odometer digits and steppers.

Live specimen

Loading demo…

Guidance

When to use

  • Any bounded numeric value with clear increment and decrement controls, such as seats, quantity, or retries.
  • Values typed often and stepped often, where arrow keys, PageUp/PageDown, and hold-to-repeat speed up entry.
  • Prefixed or suffixed amounts where the unit should follow the value.

When not to use

  • Use input for free-form numeric strings like serials or codes.
  • Use slider when the value is picked from a range by position rather than typed.
  • Use several fields with one save button — number-field commits every change as it happens.

Installation

pnpm add sveltearc

Usage

<script lang="ts">
  import { NumberField } from '$lib/components/number-field';
  let seats = $state(4);
</script>

<NumberField label="Seats" bind:value={seats} min={1} max={12} suffix={(n) => n === 1 ? ' seat' : ' seats'} description="How many people need access." />

Variants & Examples

Default

Standard appearance and configuration.

<NumberField label="Seats" />

API Reference

PropTypeDefaultDescription
labelstring–(Required) Visible label, tied to the input. Doubles as the scrub handle when scrub is set.
valuenumber–Controlled value. Omit (with defaultValue) for uncontrolled use; bindable.
defaultValuenumber0Initial value for uncontrolled use.
onValueChange(value: number) => void–Fires with the clamped, snapped value on every committed change.
minnumber0Lower bound.
maxnumberNumber.MAX_SAFE_INTEGERUpper bound.
stepnumber1Step size. Non-positive values fall back to 1.
largeStepnumberten stepsPageUp, PageDown, and Shift with an arrow move this far.
descriptionstring–Helper copy under the field, linked through aria-describedby.
disabledbooleanfalseDisables the input and both step buttons.
idstring$props.id()Element id. Falls back to an auto-generated id.
prefixstring | ((value: number) => string)–Text before the number, such as "$".
suffixstring | ((value: number) => string)–Text after the number, such as " seats".
scrubbooleanfalseDrag the label sideways to scrub the value, one step every few pixels.
localestring"en-US"Formatting locale. Fixed by default so server and client render the same digits.
formatOptions{ minimumFractionDigits?: number; maximumFractionDigits?: number; useGrouping?: boolean }–Fraction digits and grouping. Fraction digits follow the precision of step by default.
size"sm" | "md" | "lg""md"Control height, type size, and default width.
limitHintboolean | ((edge: "min" | "max", limit: number) => string)trueA short note beside the label when a press meets a limit. false hides it; a function writes the copy.

Keyboard Interactions

KeyAction
ArrowUp / ArrowDownSteps the value; hold to repeat. Shift takes a large step.
PageUp / PageDownTakes a large step.
Home / EndJumps to the minimum or maximum.
EnterCommits a typed draft, or selects the value when idle.
EscapeRolls a typed draft back to the value it started from.
TabMoves focus to the interactive element.

Accessibility

  • Renders a native text input with role="spinbutton" plus aria-valuenow, aria-valuetext, aria-valuemin, and aria-valuemax.
  • Description and limit-note ids are merged into aria-describedby alongside any caller value; a typed value past a limit sets aria-invalid.
  • Limit presses announce which limit was met through a polite live region; animated digits are aria-hidden with a plain screen reader value.

Motion

Phase 1 still-port: odometer wheels, affix rolls, limit-note slide, value strain, and helper word-rise render their static end states; Phase 2 wires the springs without restructuring.

Notes for AI

  • Bounded numeric entry with steppers. Commits every change live — do not use where several fields must save together.
  • Works controlled or with defaultValue; onValueChange always receives the clamped, snapped value.
  • Typed drafts apply live while valid; derive limit copy with limitHint instead of custom messages.

Source of truth: registry/components/number-field/number-field.tsx