Skip to content

Number Input

A numeric field with − and + buttons. Digits stay left to right and isolated, and Arabic-Indic digits typed in become Western digits.

Version 0.4.1 ·

Number Input states

Installation

bunx --bun shadcn@latest add @dga/number-input

Installs Input and Field too.

Usage

page.tsx
tsx
import { Field, FieldLabel } from "@/components/ui/field";
import { NumberInput } from "@/components/ui/number-input";

export function QuantityField() {
  return (
    <Field name="quantity">
      <FieldLabel>Quantity</FieldLabel>
      <NumberInput
        defaultValue={1}
        min={1}
        max={10}
        decrementLabel="Decrease"
        incrementLabel="Increase"
      />
    </Field>
  );
}

Use Number Input for quantities and amounts people nudge up and down. For identifiers that are digits but not quantities (a National ID, a phone number) use Input with inputMode="numeric". Always translate decrementLabel and incrementLabel on Arabic pages.

Anatomy

  • Box: the same border and fill as Input.
  • Decrease button (−): on the start side.
  • Input: the number, centred between the buttons, dir="ltr" with unicode-bidi: isolate.
  • Increase button (+): on the end side.

Examples

Min, max and step

min and max stop the buttons and the arrow keys at the limits, and the button at the limit is disabled. step sets how much each press changes the value. The value you receive is always a number.

Min, max and step

States

Default, filled, read-only, disabled and error, and the medium size with a darker fill.

States

Formatting

format takes Intl.NumberFormat options for currency, percent and units. The default locale is en-u-nu-latn, so digits are Western on every page.

page.tsx
tsx
<NumberInput
  defaultValue={250}
  format={{ style: "currency", currency: "SAR", currencyDisplay: "code" }}
/>

RTL

The buttons swap sides: − on the right (start) and + on the left (end). The digits do not move or reorder: the field is dir="ltr", isolated from the Arabic text around it. Typing ٣ gives 3; the decimal mark ٫ gives a point.

LTR (English)
RTL (Arabic)

Accessibility

KeyAction
TabMoves focus to the field. The buttons are skipped; the arrow keys do the same.
ArrowUp / ArrowDownIncrease or decrease by step.
Shift + ArrowUp / ArrowDownChange by 10 (largeStep). With Alt, by 0.1 (smallStep).
Home / EndJump to min or max, when they are set.
  • The field is a text input with inputmode="numeric" and aria-roledescription="Number field", so phones show a numeric keyboard and screen readers announce a number field.
  • The − and + buttons have names (decrementLabel, incrementLabel) and are 24px targets. They are disabled at the limits.
  • dir="ltr" and unicode-bidi: isolate keep digits and signs in order inside Arabic text.
  • Arabic-Indic and Persian digits are accepted when typed or pasted and become Western digits; screen readers read the Western value.

API Reference

NumberInput

A numeric field with − and + buttons. Digits are always written left to right, also on Arabic pages, and typing Arabic-Indic digits (٣) gives Western digits (3). Label it with a Field.

NumberInput props
PropTypeDefault
"md" | "lg"

Height: `md` 32px, `lg` 40px.

No default
"default" | "lighter" | "darker"

Fill style. `lighter` and `darker` keep the border.

No default
string

Hint text shown while the field is empty.

No default
string

Identifies the field when a form is submitted.

No default
string

Accessible name of the decrease button. Translate it on Arabic pages.

"Decrease"
string

Accessible name of the increase button. Translate it on Arabic pages.

"Increase"
boolean

Hides the − and + buttons and leaves a plain numeric field.

false
string

Class names for the native `<input>`. `className` styles the box around it.

No default
string

The locale of the input element. Defaults to the user's runtime locale.

"en-u-nu-latn"
number | null

The raw numeric value of the field.

No default
number

Amount to increment and decrement with the buttons and arrow keys, or to scrub with pointer movement in the scrub area. To always enable step validation on form submission, specify the `min` prop explicitly in conjunction with this prop. Specify `step="any"` to always disable step validation; interactive stepping then uses a base amount of `1`, while the alt and shift keys still step by `smallStep` and `largeStep`.

No default
number

The uncontrolled value of the field when it's initially rendered. To render a controlled number field, use the `value` prop instead.

No default
boolean

Whether the component should ignore user interaction.

No default
Intl.NumberFormatOptions

Options to format the input value.

No default
number

The maximum value of the input element.

No default
number

The minimum value of the input element.

No default
(value: number | null, details) => void

Callback fired when the number value changes. The `eventDetails.reason` indicates what triggered the change: - `'input-change'` for parseable typing or programmatic text updates - `'input-clear'` when the field becomes empty - `'input-blur'` when formatting (and clamping, if enabled) occurs on blur - `'input-paste'` for paste interactions - `'keyboard'` for arrow-key/Home/End stepping (typing digits uses `'input-change'`/`'input-clear'`) - `'increment-press'` / `'decrement-press'` for button presses on the increment and decrement controls - `'wheel'` for wheel-based scrubbing - `'scrub'` for scrub area drags

No default
boolean

Whether the user should be unable to change the field value.

No default
boolean

Whether the user must enter a value before submitting a form.

No default

Also accepts every prop of the Base UI NumberField (opens in a new tab).

Deviations from DGA

  • Digits are always Western, left to right and isolated. Figma does not describe Arabic pages.
  • The − and + buttons are real buttons on the logical start and end sides.
ChangeReason
Number Input: digits and directionNumbers are technical values (amounts, IDs, quantities) that must not be reordered or mixed with other digit systems in one field.

View all 1 deviations for this component

Contrast

282 pairs checked · all pass WCAG 2.1 AAShow 47 states
Variant / stateLightDark
filled text18.09:1 AAA15.87:1 AAA
hovered text18.09:1 AAA15.87:1 AAA
focused text10.17:1 AAA12.04:1 AAA
pressed text9.24:1 AAA9.64:1 AAA
pressed placeholder6.69:1 AA5.06:1 AA
pressed prefix and suffix text9.24:1 AAA9.64:1 AAA
underline, focused and pressed18.74:1 3:116.80:1 3:1
placeholder4.77:1 AA6.32:1 AA
prefix and suffix text10.17:1 AAA12.04:1 AAA
icon4.77:1 3:19.38:1 3:1
border, default4.57:1 3:13.80:1 3:1
border, hovered9.73:1 3:112.04:1 3:1
border, focused and pressed17.93:1 3:116.80:1 3:1
underline, error4.62:1 3:14.65:1 3:1
border, error4.62:1 3:14.65:1 3:1
filled text (lighter)17.64:1 AAA14.42:1 AAA
hovered text (lighter)17.64:1 AAA14.42:1 AAA
focused text (lighter)10.17:1 AAA12.04:1 AAA
pressed text (lighter)9.24:1 AAA9.64:1 AAA
pressed placeholder (lighter)6.69:1 AA5.06:1 AA
pressed prefix and suffix text (lighter)9.24:1 AAA9.64:1 AAA
underline, focused and pressed (lighter)18.28:1 3:115.27:1 3:1
placeholder (lighter)4.65:1 AA5.75:1 AA
prefix and suffix text (lighter)9.92:1 AAA10.95:1 AAA
icon (lighter)4.65:1 3:18.52:1 3:1
border, hovered (lighter)9.73:1 3:112.04:1 3:1
border, focused and pressed (lighter)17.93:1 3:116.80:1 3:1
underline, error (lighter)4.62:1 3:14.65:1 3:1
border, error (lighter)4.62:1 3:14.65:1 3:1
filled text (darker)16.44:1 AAA17.04:1 AAA
hovered text (darker)16.44:1 AAA17.04:1 AAA
focused text (darker)10.17:1 AAA12.04:1 AAA
pressed text (darker)9.24:1 AAA9.64:1 AAA
pressed placeholder (darker)6.69:1 AA5.06:1 AA
pressed prefix and suffix text (darker)9.24:1 AAA9.64:1 AAA
underline, focused and pressed (darker)17.03:1 3:118.03:1 3:1
placeholder (darker)6.69:1 AA6.79:1 AA
prefix and suffix text (darker)9.24:1 AAA12.93:1 AAA
icon (darker)4.34:1 3:110.07:1 3:1
border, hovered (darker)9.73:1 3:112.04:1 3:1
border, focused and pressed (darker)17.93:1 3:116.80:1 3:1
underline, error (darker)4.62:1 3:14.65:1 3:1
border, error (darker)4.62:1 3:14.65:1 3:1
border, hover and focus (lighter and darker)4.57:1 3:13.80:1 3:1
read-only text18.09:1 AAA15.87:1 AAA
stepper icon18.09:1 3:115.87:1 3:1
stepper icon, hovered16.44:1 3:112.71:1 3:1

Text needs 4.5:1, icons and focus rings 3:1. Disabled states are exempt. Full contrast report