Number Input
import { NumberInput } from '@absoluteui/components/number-input';import '@absoluteui/components/number-input/style.css';Using Angular? See Number Input for Angular.
What it does
Section titled “What it does”Locale-aware numeric entry. NumberInput renders a text <input> with the spinbutton role (not native type=number, which ignores the locale’s separators and differs across browsers). It parses and formats group and decimal separators, locale digits (for example Arabic-Indic), minus-sign variants, and currency, percent, and unit literals through Intl.NumberFormat. It steps with the keyboard and optional stepper buttons, validates range and step with localized messages, and submits a canonical value through a hidden input.
When to use
Section titled “When to use”- Quantities, amounts, prices, percentages, and measurements users type or adjust in small increments.
- Numbers that must be entered and displayed in the user’s locale (1.234,5 in German, Arabic-Indic digits in Arabic).
- Values constrained by a minimum, maximum, or step.
When not to use
Section titled “When not to use”- Identifiers made of digits (card numbers, postal codes, phone numbers, one-time codes): use Text Input with inputMode ‘numeric’.
- Picking a value on a continuous range where precision does not matter: use Slider.
- Dates and times: use Date Input or Date Picker.
Examples
Section titled “Examples”Quantity with bounds
Section titled “Quantity with bounds”import { NumberInput } from '@absoluteui/components/number-input';
const host = document.createElement('div');document.body.append(host);const quantity = NumberInput.create(host, { label: 'Quantity', name: 'quantity', value: 1, min: 1, max: 99, required: true });host.addEventListener('aui-change', () => { host.dataset.quantity = String(quantity.value ?? '');});Price in euros, German locale
Section titled “Price in euros, German locale”import { NumberInput } from '@absoluteui/components/number-input';
const host = document.createElement('div');document.body.append(host);NumberInput.create(host, { label: 'Preis', name: 'price', locale: 'de-DE', min: 0, step: 0.01, format: { style: 'currency', currency: 'EUR', maximumFractionDigits: 2 },});Percentage (typed 15 means 0.15)
Section titled “Percentage (typed 15 means 0.15)”import { NumberInput } from '@absoluteui/components/number-input';
const host = document.createElement('div');document.body.append(host);NumberInput.create(host, { label: 'Discount', min: 0, max: 1, step: 0.001, format: { style: 'percent', maximumFractionDigits: 1 }, validate: (value) => (value !== null && value > 0.5 ? 'Discounts above 50% need approval.' : null),});Hide stepper buttons globally
Section titled “Hide stepper buttons globally”import { configureAbsoluteUI } from '@absoluteui/core';
configureAbsoluteUI({ components: { numberInput: { showSteppers: false, validationTiming: 'blur' } } });import { NumberInput } from '@absoluteui/components/number-input';import '@absoluteui/components/number-input/style.css';
const host = document.querySelector<HTMLElement>('#quantity');if (host) { const quantity = NumberInput.create(host, { label: 'Quantity', name: 'quantity', value: 1, min: 1, max: 99, required: true }); host.addEventListener('aui-change', () => { if (!quantity.validity.valid) quantity.reportValidity(); });}More usage
Section titled “More usage”import { NumberInput } from '@absoluteui/components/number-input';
const host = document.createElement('div');document.body.append(host);NumberInput.create(host, { label: 'Preis', name: 'price', locale: 'de-DE', min: 0, step: 0.01, format: { style: 'currency', currency: 'EUR', maximumFractionDigits: 2 } });Anatomy
Section titled “Anatomy”<div class="aui-field aui-number-input" data-aui-control> (consumer host) <label class="aui-field__label" for=input>Label<span class="aui-field__required" aria-hidden="true">*</span></label> <div class="aui-number-input__control"> <input class="aui-number-input__input" type="text" role="spinbutton" inputmode="decimal" aria-valuenow aria-valuetext aria-valuemin aria-valuemax> <button class="aui-number-input__stepper aui-number-input__decrement" tabindex="-1"> (showSteppers) <button class="aui-number-input__stepper aui-number-input__increment" tabindex="-1"> (showSteppers) </div> <input type="hidden" name value="1234.5"> (canonical value) <div class="aui-field__description"> (optional) <div class="aui-field__error aui-validation-message"> (when shown)</div>API reference
Section titled “API reference”Factory
Section titled “Factory”NumberInput.create(host: HTMLElement, options: NumberInputOptions): NumberInputGlobal configuration key: numberInput (see Configuration).
Options
Section titled “Options”| Option | Type | Default | Description |
|---|---|---|---|
label |
string |
Visible label and accessible name. Required. | |
hideLabel |
boolean |
false |
Visually hide the label; it remains the accessible name. |
value |
number | null |
null |
Initial value; also the value restored by reset(). |
name |
string |
Form field name, set on the hidden input that carries the canonical value. | |
placeholder |
string |
Supplementary hint; never replaces the label. | |
description |
string |
Help text referenced by aria-describedby. | |
required |
boolean |
false |
Empty value is invalid; shows the required indicator. |
disabled |
boolean |
false |
Disables the input and steppers; the value is not submitted. |
readOnly |
boolean |
false |
Value is focusable and submitted but not editable or steppable. |
min |
number |
Minimum value; also the step base and the Home target. | |
max |
number |
Maximum value; End goes to the largest on-grid value <= max. | |
step |
number | null |
1 |
Increment and step-validation grid (base min ?? 0). Default 0.01 for percent style. null: any value, increments of 1. |
format |
{ minimumFractionDigits?: number; maximumFractionDigits?: number; useGrouping?: boolean; style?: 'decimal' | 'percent' | 'currency' | 'unit'; currency?: string; unit?: string } |
Display format (Intl.NumberFormat subset). maximumFractionDigits also rounds the value on commit. | |
validate |
(value: number | null) => string | null |
Consumer validation; return a message to mark the value invalid. | |
icons |
Partial<{ error: IconRef; increment: IconRef; decrement: IconRef }> |
Icon slot overrides. | |
validationTiming |
'blur' | 'input' | 'submit' |
"blur" |
When validation messages first appear; afterwards they update live. Globally configurable. |
showSteppers |
boolean |
true |
Render increase/decrease buttons (pointer affordance; keyboard users use the arrow keys). Globally configurable. |
locale |
string |
"en-US" |
Parsing, formatting, and message locale. Globally configurable. Inherited from common configuration. |
Properties
Section titled “Properties”| Property | Type | Description |
|---|---|---|
host |
HTMLElement |
Consumer host; its children are owned by the component. |
input |
HTMLInputElement |
The visible text input (role=spinbutton). |
formInput |
HTMLInputElement |
Hidden input carrying the canonical value and name. |
value |
number | null |
Current parsed value; null when empty or unparseable. |
text |
string |
Text in the input (raw while typing, formatted after commit). |
name |
string | null |
Form field name. |
label |
string |
Label text. |
disabled |
boolean |
Disabled state. |
validity |
FieldValidity |
{ valid, error, message }; computed on every change. |
dirty |
boolean |
Value differs from the initial value, or the text is unparseable. |
touched |
boolean |
Focus has left the control at least once. |
options |
Readonly<NumberInputOptions> |
Current instance options. |
Methods
Section titled “Methods”| Method | Description |
|---|---|
setValue(value: number | null): void |
Programmatic value; reformats the display (no events); clears an external custom error. |
update(options: Partial<NumberInputOptions>): void |
Merge and apply options. |
reportValidity(): boolean |
Show messages now and return validity. |
setCustomError(message: string | null): void |
External error (server, cross-field rule); cleared when the user edits. |
getFormValue(): number | null |
Value for form serialization. |
focus(): void |
Focus the input. |
reset(): void |
Restore the initial value and clear dirty/touched/submitted state. |
destroy(): void |
Remove rendering, listeners, registration, and subscriptions. |
Events
Section titled “Events”| Event | Detail | Bubbles | Cancelable | Description |
|---|---|---|---|---|
aui-input |
{ value: number | null; text: string; validity: FieldValidity } |
Yes | No | User typed, or stepped (keyboard or stepper buttons, since 0.9.0); value is the parse of the raw text. |
aui-change |
{ value: number | null; validity: FieldValidity } |
Yes | No | User committed a changed value (blur, Enter, arrow/page/Home/End keys, or a stepper button). |
CSS classes
Section titled “CSS classes”All rules live in the aui.components cascade layer, so application styles override them without specificity battles.
| Class | Purpose |
|---|---|
aui-field aui-number-input |
Host. |
aui-number-input--invalid | --disabled |
State modifiers on the host. |
aui-field__label | __required | __description | __error |
Shared field chrome. |
aui-number-input__control |
Control boundary (input and stepper buttons). |
aui-number-input__input |
Visible text input. |
aui-number-input__stepper | __increment | __decrement |
Stepper buttons. |
Icon slots
Section titled “Icon slots”| Slot | Default icon |
|---|---|
error |
alert-circle |
increment |
plus |
decrement |
minus |
Design tokens
Section titled “Design tokens”Tokens this stylesheet reads (verified in CI). Override them in a theme or with the Theme Designer.
--aui-border-width | --aui-color-border | --aui-color-border-strong | --aui-color-disabled-bg | --aui-color-error-fg | --aui-color-focus-ring | --aui-color-hover | --aui-color-surface | --aui-color-surface-sunken | --aui-color-text | --aui-color-text-disabled | --aui-color-text-muted | --aui-control-height-md | --aui-control-padding-x | --aui-duration-fast | --aui-easing-standard | --aui-font-family-sans | --aui-font-size-md | --aui-font-size-sm | --aui-font-weight-medium | --aui-icon-size-sm | --aui-line-height-normal | --aui-line-height-tight | --aui-radius-md | --aui-space-1
TypeScript definitions
Section titled “TypeScript definitions”interface NumberInputFormat { minimumFractionDigits?: number; maximumFractionDigits?: number; useGrouping?: boolean; style?: 'decimal' | 'percent' | 'currency' | 'unit'; currency?: string; unit?: string;}interface NumberInputInputDetail { value: number | null; text: string; validity: FieldValidity }interface NumberInputValueDetail { value: number | null; validity: FieldValidity }interface FieldValidity { readonly valid: boolean; readonly error: ValidationErrorCode | null; readonly message: string }Accessibility
Section titled “Accessibility”Pattern: WAI-ARIA APG Spinbutton pattern on a text <input> with a <label for> association.
Keyboard
Section titled “Keyboard”| Key | Context | Action |
|---|---|---|
| Tab / Shift+Tab | Any | Moves focus to and from the input; stepper buttons are skipped. |
| ArrowUp / ArrowDown | Input focused | Increase / decrease by one step (from empty: min, else 0), clamped to [min, max]. |
| PageUp / PageDown | Input focused | Increase / decrease by ten steps. |
| Home / End | Input focused, min / max set | Set the minimum / maximum. Without the bound, native caret movement. |
| Enter | Input focused | Commit: reformat the value and fire aui-change; implicit form submission is not prevented. |
| All editing keys | Input focused | Native text editing. |
| Element | Role |
|---|---|
| input | spinbutton |
| stepper buttons | button (native), not in the tab order |
| icons | none (aria-hidden=true) |
ARIA attributes
Section titled “ARIA attributes”| Attribute | Element | Value |
|---|---|---|
aria-valuenow |
input | Current value; removed when empty or unparseable |
aria-valuetext |
input | Value formatted in the locale (for example 15% or $1,234.50) |
aria-valuemin / aria-valuemax |
input | min / max when set |
aria-describedby |
input | Help text and visible error message ids |
aria-invalid |
input | “true” only while an error message is shown |
aria-label |
stepper buttons | Localized Increase / Decrease |
aria-controls |
stepper buttons | Input id |
The control boundary shows the focus indicator (border in color-focus-ring plus a 1px ring). Pressing a stepper button keeps focus in the input.
WCAG 2.2 success criteria addressed
Section titled “WCAG 2.2 success criteria addressed”Automated test results: Accessibility and compliance.
Notes and known caveats
Section titled “Notes and known caveats”- The required asterisk is aria-hidden; required state is conveyed by the native required attribute.
- Errors appear after the first blur by default so users are not interrupted while typing.
- Validation messages format numbers in the locale and style (for example ‘Enter a value of 1.000 or more.’).
- The stepper buttons are a pointer affordance with tabindex -1; every action is available from the keyboard on the input.
- Stepper buttons are disabled at the bounds.
- Spinbutton announcements (aria-valuetext on step) have not yet been manually verified with NVDA, JAWS, and VoiceOver.
- Mobile screen readers expose adjustable spinbutton gestures inconsistently for text inputs.
Behavior
Section titled “Behavior”States
Section titled “States”default, focus-visible, empty, filled, disabled, read-only, required, invalid (message shown), bad input, at minimum (decrement disabled), at maximum (increment disabled).
State transitions
Section titled “State transitions”| From | Trigger | To |
|---|---|---|
| any | user types | text kept as typed; value parsed; bad-input when unparseable; dirty |
| editing | blur or Enter | committed: value rounded to maximumFractionDigits when set; text reformatted unless unparseable; aui-change if the value changed |
| any | arrow, page, Home/End key, or stepper | value stepped, clamped, snapped; text reformatted; aui-change |
| any | focus leaves the host | touched; messages shown (blur timing), after the pointer is released if focus left by a click |
| any | reportValidity() |
submitted; messages shown for every timing |
| any | locale or format changes while not focused | text reformatted in the new locale |
| any | reset() |
initial value, formatted; pristine, untouched |
Accessibility notes
Section titled “Accessibility notes”- The required asterisk is
aria-hidden; the nativerequiredattribute conveys the state. - Errors appear after the first blur by default, then update live.
- Messages format numbers in the locale and style:
Enter a value of 1.000 or more.in German. inputmode="decimal"(ornumericwhenmaximumFractionDigitsis 0) selects the virtual keyboard.- Stepper buttons are a pointer affordance; pressing one keeps focus in the input. Every action is available from the keyboard.
- Manual screen-reader verification is pending (see Known limitations).
Responsive behavior
Section titled “Responsive behavior”Full width of its container. The stepper buttons keep their size and the input shrinks. Density tokens change the control height and button width.
Right-to-left
Section titled “Right-to-left”Logical properties: the stepper buttons sit at the inline end in both directions. Bidi control marks that Intl inserts in RTL locales are ignored when parsing.
Theming
Section titled “Theming”Colors come from role tokens only. In forced-colors mode, focus draws a Highlight outline and disabled steppers use GrayText.
Global configuration
Section titled “Global configuration”configureAbsoluteUI({ components: { numberInput: { showSteppers: false, validationTiming: 'input' } } });Changes apply to existing instances immediately. A locale change reformats the text of inputs that are not focused.
Component configuration
Section titled “Component configuration”Instance options override global and theme defaults. All options are dynamic.
icons.error (default alert-circle), icons.increment (default plus), and icons.decrement (default minus).
Security
Section titled “Security”All text uses textContent or attributes. Validation messages never include the entered text; only constraint parameters. No style attributes or HTML sinks. CSP-safe.
Data handling
Section titled “Data handling”No persistence, network access, or telemetry. The visible input is not named; a hidden input named name carries the canonical value (1234.5, empty when null or unparseable, and not submitted while disabled).
Performance
Section titled “Performance”Four listeners per instance (input, blur, keydown, focusout), two per stepper button, and one configuration subscription. Intl.NumberFormat instances are built once per locale and format and reused. Budget: under 1 ms average per NumberInput.create across 1,000 instances.
Supported data scale
Section titled “Supported data scale”Values are JavaScript numbers: exact up to 15 significant digits and integers up to 2^53. 1,000 inputs per page is the performance target.
Edge cases
Section titled “Edge cases”- Percent style: typed
15and15%are both0.15; the default step is0.01. de-DE:1.234,5is1234.5; a lone.is a group separator, so1.5is15.ar-EG:١٬٢٣٤٫٥and ASCII1234.5are both accepted; the display uses Arabic-Indic digits.-5(U+2212),-5, and a leading+are accepted.- Currency:
$1,234.50,1234.5, andUSD 1234.5parse to the same value. - Unparseable text (
12abc, two decimal separators) isbad-input:valueisnull, the text is kept for correction, and the hidden input is empty. - Stepping from an off-grid value snaps to the grid in the step direction (
8with step 5 from min 1 goes up to 11, down to 6); the result is rounded to the step’s decimal places (0.2 + 0.1steps to0.3). maximumFractionDigitsrounds the committed value with decimal (not binary) rounding:1234.505becomes1234.51.
Errors
Section titled “Errors”| Code | Category | Cause |
|---|---|---|
NUMBER_INPUT_OPTIONS |
configuration | Missing label; non-finite value, min, or max; min > max; step <= 0; invalid format fields (style, missing currency/unit, fraction digits). |
NUMBER_INPUT_FORMAT |
configuration | Intl.NumberFormat rejected the format for the locale (for example an unknown unit). |
Validation (validity.error) |
validation | value-missing, bad-input, range-underflow, range-overflow, step-mismatch, custom. |
A failed construction leaves no rendering, listeners, or subscriptions behind. A failed update() keeps the previous options.
Known limitations
Section titled “Known limitations”- IEEE 754 doubles: no arbitrary-precision decimals or BigInt.
- Parsing is strict to the locale; group separator positions are not validated.
- Accounting (parenthesized negatives), scientific, engineering, and compact notation are not parsed.
- The hidden canonical value uses JavaScript number serialization (exponent notation below 1e-6 and from 1e21).
- With
inputmode="numeric"some mobile keyboards have no minus key. - Stepper buttons do not repeat on press-and-hold.
- Screen-reader behavior has not yet been manually verified with NVDA, JAWS, and VoiceOver.
Unsupported scenarios
Section titled “Unsupported scenarios”- Native
type=numberbehavior (wheel stepping, browser spin buttons). - HTML in the label, help text, or messages.
- BigInt values.