Skip to content

NumberField

Terminal window
$ npx shadcn@latest add https://lacodda.github.io/dowel/r/number-field.json
See it liveWith a stepper and without, with a unit, and formatted as a currency.→

Not <input type="number">. The native one draws its own spinner where no stylesheet reaches, rejects a pasted 1 234,50, and on some browsers silently blanks itself on anything it dislikes. This is a text box that tells the keyboard it is numeric and names itself as a number field, which is what a screen reader announces.

Empty is null, not zero. “No number” and “the number zero” are different answers — no price yet and free — and a field that returns 0 for an empty box makes them the same the moment it saves.

<NumberField value={price} onValueChange={setPrice} min={0} />

The unit is a caption, not part of the value. Inside the input it is something to parse and something to delete by accident; beside it, it cannot be typed into and the value stays a number.

<NumberField value={width} onValueChange={setWidth} unit="px" />

How it reads is Intl, not a hand-rolled separator. A field showing 1234.5 to someone who writes 1 234,5 is one they translate in their head.

<NumberField value={total} format={{ style: 'currency', currency: 'EUR' }} />

locale is left alone by default, which means the application’s language — the nearest LocaleProvider or the page’s <html lang> (see no-implicit-locale). State one only when the figure belongs to a place rather than to a person.

The stepper’s buttons are aria-hidden. The field already announces its value and its range; two more unlabelled controls tell a reader nothing it did not have. Use hideStepper where the range is wide enough that the buttons are an invitation to click sixty times.

A typed number out of range is moved to the bound on blur - Base UI’s default, and a correction made without a word: a 9 typed for an age of at least 13 leaves the box as 13. A form that says what is wrong passes allowOutOfRange, keeps the 9 and says it; the arrows still stop at the bounds. SchemaFields always does.

Props

Read from the types in ui/number-field.tsx, the file a product copies - so the description of each prop is also what an editor shows over it there.

PropTypeDefault
valuenumber | nullThe controlled number. null is an empty box.
defaultValuenumberThe starting number of an uncontrolled field.
onValueChange(value: number | null) => voidCalled with the new number as it changes, or null when the box is emptied.
minnumberThe lowest the number may be. The arrows stop here, and a typed number below it moves to it on blur unless allowOutOfRange is set.
maxnumberThe highest the number may be. See min.
allowOutOfRangebooleanKeep a typed number outside min/max as typed, rather than moving it to the bound on blur - for a form that says what is wrong instead. A 9 typed for an age of at least 13 became 13 without a word; the arrows still stop at the bounds.
stepnumberWhat the arrows change it by.
largeStepnumberWhat PageUp and PageDown change it by, when a single step is too slow.
formatNumberFormatOptionsHow the number reads: Intl.NumberFormat options, so a currency or a percentage is a prop rather than a wrapper.
localestringWhich conventions format follows. Left alone it is the application's language (see useLocale); a product states one only when the figure belongs to a place rather than to a person - a price in a fixed market.
unitReactNodeWhat the number is in - px, kg, %. A caption beside the field, not part of the value.
hideStepperbooleanfalseHide the stepper. For a field with a wide range, where the buttons are an invitation to click sixty times.
disabledbooleanTurns the field off: it cannot be focused or changed.
readOnlybooleanThe number can be read and selected but not changed.
requiredbooleanMarks the field as required, for a form to check.
namestringThe name the number is submitted under in a form.
placeholderstringShown in the box while it is empty.
aria-labelstringNames the field for a screen reader, when there is no visible label.
classNamestring

Accessibility

The examples on the stand, through axe 4.14.0 in a browser, in both themes, against WCAG 2.2 A and AA and axe's best practices: 19 rules applied and passed, and none failed. The accessibility report has every item.

History

Each release that changed ui/number-field.tsx - the file a product copies - newest first.

  1. v0.41.0
    • docs read every props table from the component's types ee3b159
  2. v0.37.0
    • feat keep a typed number out of range when asked 998c9b4
    • fix leave a field's wiring on the input, not on the group 562d960
  3. v0.36.0
    • fix set typed text at 16px under a finger 967db1d
  4. v0.31.0breaking
    • feat dates and numbers in the application's language 9be5f95
  5. v0.28.0breaking
    • feat density as a property of a region 849d783
  6. v0.17.0
    • feat numbers, scales, and the two fields with no foundation ead7461