Skip to main content

Number Input

Info:Form html svelte astro Success: coverage 25/25

A numeric field with stepper buttons, bounds, and step snapping, driven by pointer or keyboard.

Live demo

live · @xoji/astro

Number Input

Common fields

Quantity
Step by 5
Price

Alternate step

Volume

Shift, PageUp/Dn → ±10

Fine tune

plain → ±0.1, Shift → ±1

Sizes & disabled

Small

sm

Large

lg

Locked

disabled

Number Input edits a single number. A role="spinbutton" text field sits between decrease and increase buttons; the buttons, the up/down arrow keys, and direct typing all change the value, which snaps to step and clamps to [min, max] on commit.

It is form-associated; give it a name and its value submits with the form. It accepts decimals (e.g. a 0.01 step for currency). A second granularity rides alongside step: holding the modifier (Shift by default) on a click or arrow applies altStep, ten times step out of the box, while PageUp/PageDown always jump by it, and altDefault flips which one is primary. Out-of-range typing reverts on commit, and the stepper buttons disable at the bounds. Three sizes: sm, the default md, and lg.

When to use

How this component composes with the rest of the set.

Pair with Field or a form to capture the value; give it a name so it submits.
Use step="0.01" with min="0" for currency, or a wide range with step="1" for counts.
For an unbounded continuous range with a visible track, reach for Slider instead.

Props

13 props, straight from the manifest.

PropTypeDefaultBindingsDescription
value number
html svelte astro
The current value, snapped to `step` and clamped to the bounds on commit. Empty is allowed. Reflected and form-submitted.
min number
html svelte astro
Lower bound; the decrease button disables here.
max number
html svelte astro
Upper bound; the increase button disables here.
step number 1
html svelte astro
Granularity for the buttons and arrow keys; values snap to it. Use `0.01` for currency.
altStep number step × 10
html svelte astro
The alternate granularity, applied when the modifier is held on a click or arrow (and by `PageUp`/`PageDown` always). Defaults to ten times `step`; set it to anything, including a finer value below `step`.
altDefault boolean false
html svelte astro
Swaps which step is primary: when set, plain clicks and arrows use `altStep`, and the modifier falls back to `step`; coarse-by-default with a fine modifier is as easy as the reverse.
modifier "shift" | "alt" | "ctrl" | "meta"
shift alt ctrl meta
shift
html svelte astro
The key that swaps to the alternate step on a click or arrow press.
disabled boolean false
html svelte astro
Disables typing and the steppers; mutes the control.
size Size
sm md lg
md
html svelte astro
Control size: `sm`, `md`, or `lg`.
label string
html svelte astro
Visible label, also the accessible name via `aria-labelledby`.
labelledby string
html svelte astro
ID of an external element that names the field. Takes precedence over `label`.
name string
html svelte astro
Form field name; the value submits with the form.
placeholder string
html svelte astro
Placeholder shown when the field is empty.

Appearance

Sizes

sm

.xoji-number--sm

Compact.

md

default
.xoji-number

Default.

lg

.xoji-number--lg

Large.

States

focus-within

.xoji-number__control:focus-within

The input is focused. The control border takes the accent and a token ring appears.

step-hover

.xoji-number__step:hover

Pointer over a stepper button: the hover tint.

step-disabled

.xoji-number__step:disabled

A stepper button at its bound: muted and non-interactive.

disabled

.xoji-number--disabled

The whole field disabled: muted control, no typing or stepping.

Anatomy

The named parts that make up the component, with their selectors.

number

.xoji-number

The wrapper carrying the size and disabled classes and stacking the optional label over the control.

--font-sans --fg-0 --space-2

control

.xoji-number__control

The bordered row grouping the stepper buttons and the input; shows the focus ring when the input is focused.

--border-thin --line-2 --radius-md --bg-0 --accent --border-thick --ring

input

.xoji-number__input

The role="spinbutton" text field, centered, accepting typed numbers.

--fg-0 --text-body --space-1 --space-2

step

.xoji-number__step

The decrease and increase buttons; out of the tab order (the input is the focus stop), disabled at the bounds.

--space-6 --neutral-bg --fg-1 --text-body --state-hover --state-press --fg-disabled --state-disabled

label

.xoji-number__label

The optional visible label, referenced as the field's accessible name.

--fg-1 --text-sm

Tokens & coverage

What the component consumes, checked live against what the algorithm produces.

Success:fully covered 25/25 consumed tokens produced default register: 276 tokens

Live coverage check against the xoji-default register (derive(xojiDefault, { anchors })coverComponent(manifest, register)). Every token this component consumes must be a key the algorithm produces.

--accent --bg-0 --border-thick --border-thin --duration-fast --ease-standard --fg-0 --fg-1 --fg-disabled --font-sans --line-2 --neutral-bg --radius-md --ring --space-1 --space-2 --space-3 --space-5 --space-6 --space-7 --state-disabled --state-hover --state-press --text-body --text-sm

Accessibility

The field is role="spinbutton" with aria-valuenow (and aria-valuemin/aria-valuemax when bounds are set) kept in sync.
Up and down arrow keys nudge by step, or altStep while the modifier is held; PageUp/PageDown always jump by altStep; Enter commits typed input; the keys' default behavior is handled so the value stays in range.
The stepper buttons are out of the tab order (the input is the single focus stop) and are labeled Decrease / Increase; they disable at the bounds.
Requires an accessible name: labelledby wins, then label; the binding warns at runtime when neither is present.
Focus is shown on the control via :focus-within. The input's own outline is suppressed in favor of the grouped ring.
disabled blocks typing and stepping and mutes the control.

Code

Bounds, steps, and form association

A bounded quantity, a stepped range, a currency field, and a disabled one.

<xoji-number-input label="Quantity" value="1" min="0" max="99"></xoji-number-input>

<xoji-number-input label="Step by 5" value="20" min="0" max="100" step="5"></xoji-number-input>

<xoji-number-input label="Price" value="9.99" min="0" step="0.01"></xoji-number-input>

<xoji-number-input label="Locked" value="42" disabled></xoji-number-input>
<script lang="ts">
	import { NumberInput } from "@xoji/svelte";

	let qty = $state(1);
</script>

<NumberInput label="Quantity" bind:value={qty} min={0} max={99} />

<NumberInput label="Step by 5" value={20} min={0} max={100} step={5} />

<NumberInput label="Price" value={9.99} min={0} step={0.01} />

<NumberInput label="Locked" value={42} disabled />
---
import { NumberInput } from "@xoji/astro";
---

<NumberInput label="Quantity" value={1} min={0} max={99} />

<NumberInput label="Step by 5" value={20} min={0} max={100} step={5} />

<NumberInput label="Price" value={9.99} min={0} step={0.01} />

<NumberInput label="Locked" value={42} disabled />

Alternate step and modifier

A volume that jumps by ten on Shift (or PageUp/PageDown), and a fine-tuner that steps by 0.1 plainly with Shift falling back to whole units via altDefault.

<xoji-number-input label="Volume" value="50" min="0" max="100" alt-step="10"></xoji-number-input>

<xoji-number-input label="Fine tune" value="1" step="1" alt-step="0.1" alt-default></xoji-number-input>
<NumberInput label="Volume" value={50} min={0} max={100} altStep={10} />

<NumberInput label="Fine tune" value={1} step={1} altStep={0.1} altDefault />
<NumberInput label="Volume" value={50} min={0} max={100} altStep={10} />

<NumberInput label="Fine tune" value={1} step={1} altStep={0.1} altDefault />