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 ·
Installation
bunx --bun shadcn@latest add @dga/number-inputUsage
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"withunicode-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.
States
Default, filled, read-only, disabled and error, and the medium size with a darker fill.
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.
<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.
Accessibility
| Key | Action |
|---|---|
| Tab | Moves focus to the field. The buttons are skipped; the arrow keys do the same. |
| ArrowUp / ArrowDown | Increase or decrease by step. |
| Shift + ArrowUp / ArrowDown | Change by 10 (largeStep). With Alt, by 0.1 (smallStep). |
| Home / End | Jump to min or max, when they are set. |
- The field is a text input with
inputmode="numeric"andaria-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"andunicode-bidi: isolatekeep 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.
| Prop | Type | Default |
|---|---|---|
"md" | "lg"Height: `md` 32px, `lg` 40px. | No default | |
| ||
"default" | "lighter" | "darker"Fill style. `lighter` and `darker` keep the border. | No default | |
| ||
stringHint text shown while the field is empty. | No default | |
| ||
stringIdentifies the field when a form is submitted. | No default | |
| ||
stringAccessible name of the decrease button. Translate it on Arabic pages. | "Decrease" | |
| ||
stringAccessible name of the increase button. Translate it on Arabic pages. | "Increase" | |
| ||
booleanHides the − and + buttons and leaves a plain numeric field. | false | |
| ||
stringClass names for the native `<input>`. `className` styles the box around it. | No default | |
| ||
stringThe locale of the input element. Defaults to the user's runtime locale. | "en-u-nu-latn" | |
| ||
number | nullThe raw numeric value of the field. | No default | |
| ||
numberAmount 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 | |
| ||
numberThe uncontrolled value of the field when it's initially rendered. To render a controlled number field, use the `value` prop instead. | No default | |
| ||
booleanWhether the component should ignore user interaction. | No default | |
| ||
Intl.NumberFormatOptionsOptions to format the input value. | No default | |
| ||
numberThe maximum value of the input element. | No default | |
| ||
numberThe minimum value of the input element. | No default | |
| ||
(value: number | null, details) => voidCallback 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 | |
| ||
booleanWhether the user should be unable to change the field value. | No default | |
| ||
booleanWhether 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.
| Change | Reason |
|---|---|
Number Input: digits and direction | Numbers are technical values (amounts, IDs, quantities) that must not be reordered or mixed with other digit systems in one field. |
Contrast
282 pairs checked · all pass WCAG 2.1 AAShow 47 statesHide
| Variant / state | Light | Dark |
|---|---|---|
| filled text | 18.09:1 AAA | 15.87:1 AAA |
| hovered text | 18.09:1 AAA | 15.87:1 AAA |
| focused text | 10.17:1 AAA | 12.04:1 AAA |
| pressed text | 9.24:1 AAA | 9.64:1 AAA |
| pressed placeholder | 6.69:1 AA | 5.06:1 AA |
| pressed prefix and suffix text | 9.24:1 AAA | 9.64:1 AAA |
| underline, focused and pressed | 18.74:1 3:1 | 16.80:1 3:1 |
| placeholder | 4.77:1 AA | 6.32:1 AA |
| prefix and suffix text | 10.17:1 AAA | 12.04:1 AAA |
| icon | 4.77:1 3:1 | 9.38:1 3:1 |
| border, default | 4.57:1 3:1 | 3.80:1 3:1 |
| border, hovered | 9.73:1 3:1 | 12.04:1 3:1 |
| border, focused and pressed | 17.93:1 3:1 | 16.80:1 3:1 |
| underline, error | 4.62:1 3:1 | 4.65:1 3:1 |
| border, error | 4.62:1 3:1 | 4.65:1 3:1 |
| filled text (lighter) | 17.64:1 AAA | 14.42:1 AAA |
| hovered text (lighter) | 17.64:1 AAA | 14.42:1 AAA |
| focused text (lighter) | 10.17:1 AAA | 12.04:1 AAA |
| pressed text (lighter) | 9.24:1 AAA | 9.64:1 AAA |
| pressed placeholder (lighter) | 6.69:1 AA | 5.06:1 AA |
| pressed prefix and suffix text (lighter) | 9.24:1 AAA | 9.64:1 AAA |
| underline, focused and pressed (lighter) | 18.28:1 3:1 | 15.27:1 3:1 |
| placeholder (lighter) | 4.65:1 AA | 5.75:1 AA |
| prefix and suffix text (lighter) | 9.92:1 AAA | 10.95:1 AAA |
| icon (lighter) | 4.65:1 3:1 | 8.52:1 3:1 |
| border, hovered (lighter) | 9.73:1 3:1 | 12.04:1 3:1 |
| border, focused and pressed (lighter) | 17.93:1 3:1 | 16.80:1 3:1 |
| underline, error (lighter) | 4.62:1 3:1 | 4.65:1 3:1 |
| border, error (lighter) | 4.62:1 3:1 | 4.65:1 3:1 |
| filled text (darker) | 16.44:1 AAA | 17.04:1 AAA |
| hovered text (darker) | 16.44:1 AAA | 17.04:1 AAA |
| focused text (darker) | 10.17:1 AAA | 12.04:1 AAA |
| pressed text (darker) | 9.24:1 AAA | 9.64:1 AAA |
| pressed placeholder (darker) | 6.69:1 AA | 5.06:1 AA |
| pressed prefix and suffix text (darker) | 9.24:1 AAA | 9.64:1 AAA |
| underline, focused and pressed (darker) | 17.03:1 3:1 | 18.03:1 3:1 |
| placeholder (darker) | 6.69:1 AA | 6.79:1 AA |
| prefix and suffix text (darker) | 9.24:1 AAA | 12.93:1 AAA |
| icon (darker) | 4.34:1 3:1 | 10.07:1 3:1 |
| border, hovered (darker) | 9.73:1 3:1 | 12.04:1 3:1 |
| border, focused and pressed (darker) | 17.93:1 3:1 | 16.80:1 3:1 |
| underline, error (darker) | 4.62:1 3:1 | 4.65:1 3:1 |
| border, error (darker) | 4.62:1 3:1 | 4.65:1 3:1 |
| border, hover and focus (lighter and darker) | 4.57:1 3:1 | 3.80:1 3:1 |
| read-only text | 18.09:1 AAA | 15.87:1 AAA |
| stepper icon | 18.09:1 3:1 | 15.87:1 3:1 |
| stepper icon, hovered | 16.44:1 3:1 | 12.71:1 3:1 |
Text needs 4.5:1, icons and focus rings 3:1. Disabled states are exempt. Full contrast report