apx-dsv0.1Local renderer · live
K
Foundations4
  • Getting started
  • Theming
  • Templates
  • Icons
  • Avatar
  • Badge
  • DataGrid
  • Scheduler
  • Stat
  • Table
  • Timeline
  • TreeView
  • Accordion
  • Alert
  • EmptyState
  • Progress
  • Skeleton
  • Spinner
  • SplashScreen
  • Toast
  • ColorPicker
  • FileUpload
  • Form
  • Rating
  • TagsInput
  • Combobox
  • Field
  • Select
  • Toggle
  • Button
  • Calendar
  • Checkbox
  • DatePicker
  • Input
  • NumberInput
  • Radio
  • Slider
  • Switch
  • Textarea
  • AppShell
  • Div
  • Divider
  • Sidebar
  • Stack
  • Typography
  • Image
  • Breadcrumbs
  • Carousel
  • NavigationMenu
  • Pagination
  • Stepper
  • Tabs
  • Toolbar
  • CommandPalette
  • Confirm
  • Drawer
  • HoverCard
  • Menu
  • Modal
  • Popover
  • Tooltip
  • Icon
  • Card
  • PricingCard
60 componentsapx-ds/renderer
NumberInput
Variant↳ other

Inputs

NumberInput

Numeric form control. Typed

NumberInput

<NumberInput /> is the DS's canonical numeric form control — a drop-in replacement for <Input type="number"> that fixes its long list of UX defects (scientific notation, silent rejection on blur, no thousand separator, vendor spinners) and ships typed number | null values, locale-aware formatting + parsing, clamping + precision rounding, mouse-hold accelerating step, full keyboard control, and a hidden <input type="hidden"> for HTML form submission with the canonical value.

Overview — labeled inputs with steppers and constraints

Loading preview…
Overview.tsx

Why a separate component

Native type="number" accepts arbitrary scientific notation (1e5), silently rejects bad input on blur, lacks formatting (no thousand separators), and renders ugly browser-vendored spinners. NumberInput owns all of that:

  • Typed value (number | null) — null means the input is empty; consumers never deal with "42" strings.
  • Render as <input type="text"> under the hood + role="spinbutton" + aria-valuemin / aria-valuemax / aria-valuenow / aria-valuetext.
  • Locale-aware parsing (Intl.NumberFormat probes + Arabic-Indic / Persian digit normalization) handles 1,234.56, 1.234,56, and ١٬٢٣٤٫٥٦ uniformly.
  • Cached Intl.NumberFormat instances — formatting on hot keystrokes stays cheap.
  • Mouse-hold stepper with an accelerating repeat rate (useStepperHold) — released for the next consumer that needs press-and-hold semantics.
  • Hidden <input type="hidden"> for HTML form submission with the canonical numeric string.

Inherits Input's visual story

NumberInput re-uses inputRecipe + inputInnerRecipe directly — same variants (outline, solid, ghost, underline), same sizes (sm / md / lg), same 7-color palette, same controlBase focus ring. Adding a new Input variant or color picks up here automatically. The only new visual surface is the stepper button group (stepperGroupRecipe + stepperButtonRecipe) which lives next to the component.

Stepper position

stepperPositionLayout
'end' (default)Vertically stacked + (top) / − (bottom) on the logical end.
'start'Same stacked layout on the logical start.
'split'Single − on the logical start, single + on the logical end.

All positions use logical CSS properties, so RTL layouts flip automatically.

Keyboard

KeyAction
Arrow Up / Downvalue ± step
Shift + Arrowvalue ± largeStep (defaults to step * 10)
PageUp / PageDownvalue ± largeStep
Homevalue = min (when min defined)
Endvalue = max (when max defined)
EnterCommit + format display
EscapeRevert to last committed value

The stepper buttons are intentionally not in the tab order (tabIndex={-1}) — keyboard users operate on the input itself. Pointer-only users get the buttons.

Examples

Overview

Loading preview…
Overview.tsx

Basic — `number | null` value, default stepper

Loading preview…
Basic.tsx

Min / max / step

Loading preview…
Range.tsx

step + largeStep (Shift+Arrow)

Loading preview…
Step.tsx

Currency — Intl format pass-through

Loading preview…
Currency.tsx

Percentage — Intl percent style

Loading preview…
Percentage.tsx

Precision — decimal-place rounding

Loading preview…
Precision.tsx

Locale: de-DE — 1.234,56 display

Loading preview…
LocaleDe.tsx

Locale: ar-EG — Arabic-Indic digits

Loading preview…
LocaleAr.tsx

Scroll-wheel opt-in

Loading preview…
ScrollWheel.tsx

Stepper positions — end / start / split

Loading preview…
StepperPositions.tsx

Variants — inherits Input

Loading preview…
Variants.tsx

Sizes — sm / md / lg

Loading preview…
Sizes.tsx

Colors — 7-role palette

Loading preview…
Colors.tsx

Hide stepper buttons

Loading preview…
HiddenSteppers.tsx

Disabled + read-only

Loading preview…
Disabled.tsx

Invalid — aria-invalid + danger ring

Loading preview…
Invalid.tsx

With label + helper text

Loading preview…
WithLabel.tsx

Form submission — hidden canonical value

Loading preview…
FormSubmission.tsx

Accessibility

  • Visible input is <input type="text" role="spinbutton"> with the full ARIA value triad.
  • aria-valuetext carries the formatted display string so screen readers announce "$1,234.56" instead of the raw 1234.56.
  • Stepper buttons have translatable aria-labels (incrementLabel / decrementLabel) and are tabIndex={-1} so they don't pollute the tab order.
  • Wires through useFormFieldA11y — invalid + required + describedby flow exactly like Input.
  • Dev-only warning fires once when no aria-label / aria-labelledby / wrapping <label> is found (same shape as Input's INPUT_NO_LABEL).

Props

PropTypeDefaultDescription
allowDecimalsbooleantrueReject decimal separators entirely.
allowNegativebooleantrueReject negative input entirely (no minus sign accepted).
aria-describedbystring—Existing describedby id (helper / error text).
aria-labelstring—Accessible label fall-back for the input + the stepper buttons.
aria-labelledbystring—Existing labelledby id (e.g. from a paired `<label>`).
clampOnBlurbooleantrueSnap out-of-range values into [`min`, `max`] on blur / Enter.
classNamestring——
colorResponsiveValue<InputColor>'primary'Accent color for focus ring + stepper hover.
decrementLabelstring'Decrement'Translatable label for the `−` stepper button.
defaultValuenumber | null—Initial value for the uncontrolled flow. `null` means the field starts empty.
disabledboolean—Locks the field from edits + dims it. Stepper buttons disable too.
enableScrollWheelbooleanfalseWheel scroll changes value when the input is focused. Opt-in to avoid surprise.
formatNumberFormatOptions—Pass-through `Intl.NumberFormat` options used for the *display* string.
fullWidthResponsiveValue<boolean>trueStretch to fill the parent's inline-size.
hideStepperButtonsbooleanfalseHide the +/− buttons. Arrow keys + wheel (opt-in) still work.
idstring—Native `id` for the visible `<input>`. Auto-generated when omitted.
incrementLabelstring'Increment'Translatable label for the `+` stepper button.
invalidboolean—`aria-invalid` + danger ring/border.
largeStepnumber`step * 10`Increment for Shift+Arrow / PageUp / PageDown / Shift+wheel.
leftAddonReactNode—Visible left-edge content (icons / addons). Renders inside the input frame.
localestring—BCP-47 locale used for both parsing and formatting. Defaults to the browser locale.
maxnumber—Inclusive upper bound. When `clampOnBlur` is true, values above `max` snap to `max` on commit.
minnumber—Inclusive lower bound. When `clampOnBlur` is true, values below `min` snap to `min` on commit.
namestring—Native form name — propagates to the hidden `<input>` so submission sees a canonical value.
onBlurFocusEventHandler<HTMLInputElement>——
onChange((value: number | null) => void)—Fires on every committed change (typing + stepper + keyboard). `null` means the input is empty. Consumers receive a real `number`, never a string — this is the whole point of the component.
onChangeEnd((value: number | null) => void)—Fires on blur / Enter — useful for "save when done editing" UX.
onFocusFocusEventHandler<HTMLInputElement>——
onKeyDownKeyboardEventHandler<HTMLInputElement>——
parse((rawInput: string, locale: string) => number | null)—Custom parser. Receives the raw input string + the active locale. Returns `number | null`. When omitted, a locale-aware parser is used (handles thousand separators + Arabic-Indic / Persian digits + locale-specific decimal separators).
placeholderstring—Placeholder for the visible `<input>`.
precisionnumber—Decimal places to round to on commit. Avoid passing 0 if `allowDecimals=false` — use the latter.
readOnlyboolean—Field accepts focus but rejects edits + stepper clicks. Form still submits.
requiredboolean—Mirrors the native `required` attribute.
rightAddonReactNode—Visible right-edge content (icons / addons). Renders inside the input frame, before steppers.
sizeResponsiveValue<InputSize>'md'Visual size — inherited from Input.
stepnumber1Increment used by arrow keys and the +/− buttons.
stepperPositionenum'end'Place +/− on `start` / `end` (stacked vertically) or `split` (one each side).
styleCSSProperties——
sxSx——
valuenumber | null—Controlled value. `null` means "input is empty".
variantResponsiveValue<InputVariant>'outline'Visual variant — inherited from Input.