NumberInput

A field for a number. A text input - so what is typed is the user's until it is settled - that reads and writes the number in its locale, clamps it to a range, and steps by the arrow keys, Home/End,...

A field for a number. A text input - so what is typed is the user's until it is settled - that reads and writes the number in its locale, clamps it to a range, and steps by the arrow keys, Home/End, the mouse wheel and buttons that repeat while held. It is a role="spinbutton", so the number and its range are announced, and a hidden input carries the raw number to a form.

Every label is yours - the buttons' (incrementLabel, decrementLabel) - so the field carries no language of its own.

import { NumberInput } from '@soroush.tech/design-system/NumberInput'

;<NumberInput
  aria-label="Quantity"
  value={quantity}
  onChange={setQuantity}
  min={1}
  max={99}
  incrementLabel="Increase"
  decrementLabel="Decrease"
/>

Examples

Default

const args: Partial<NumberInputProps> = {
  defaultValue: 5,
  min: 0,
  max: 10,
  incrementLabel: 'Increase',
  decrementLabel: 'Decrease',
  variant: 'outlined',
  'aria-label': 'Quantity',
}

<NumberInput key={String(args.defaultValue)} {...args} />

With Content

px
const args: Partial<NumberInputProps> = {
  defaultValue: 5,
  min: 0,
  max: 10,
  incrementLabel: 'Increase',
  decrementLabel: 'Decrease',
  variant: 'outlined',
  'aria-label': 'Quantity',
}

<NumberInput
  {...args}
  variant="outlined"
  defaultValue={12}
  startContent={<Icon name="add" size="sm" color="inherit" />}
  endContent={<span>px</span>}
/>

Spin Buttons On The Sides

const args: Partial<NumberInputProps> = {
  defaultValue: 5,
  min: 0,
  max: 10,
  spinButtons: 'sides',
  incrementLabel: 'Increase',
  decrementLabel: 'Decrease',
  variant: 'outlined',
  'aria-label': 'Quantity',
}

<NumberInput key={String(args.defaultValue)} {...args} />

No Spin Buttons

const args: Partial<NumberInputProps> = {
  defaultValue: 5,
  min: 0,
  max: 10,
  spinButtons: 'none',
  incrementLabel: 'Increase',
  decrementLabel: 'Decrease',
  variant: 'outlined',
  'aria-label': 'Quantity',
}

<NumberInput key={String(args.defaultValue)} {...args} />

Controlled Value

Value: 5
const args: Partial<NumberInputProps> = {
  'aria-label': 'Quantity',
  defaultValue: 5,
  min: 0,
  max: 10,
  incrementLabel: 'Increase',
  decrementLabel: 'Decrease',
  variant: 'outlined',
}

<Controlled {...args} />

Change And Commit

const args: Partial<NumberInputProps> = {
  'aria-label': 'Quantity',
  defaultValue: 5,
  min: 0,
  max: 10,
  incrementLabel: 'Increase',
  decrementLabel: 'Decrease',
  variant: 'outlined',
}

<Events {...args} />

Formats

const args: Partial<NumberInputProps> = {
  'aria-label': 'Quantity',
  defaultValue: 5,
  min: 0,
  max: 10,
  incrementLabel: 'Increase',
  decrementLabel: 'Decrease',
  variant: 'outlined',
}

<Flex gap={2}>
  <NumberInput
    {...args}
    defaultValue={1234.5}
    min={undefined}
    max={undefined}
    aria-label="Plain"
  />
  <NumberInput
    {...args}
    defaultValue={1234.5}
    min={undefined}
    max={undefined}
    locale="de-DE"
    format={{ useGrouping: true }}
    aria-label="German"
  />
  <NumberInput
    {...args}
    defaultValue={0.25}
    min={0}
    max={1}
    step={0.05}
    format={{ style: 'percent' }}
    aria-label="Percent"
  />
  <NumberInput
    {...args}
    defaultValue={19.9}
    min={0}
    max={undefined}
    locale="en-US"
    format={{ style: 'currency', currency: 'USD' }}
    aria-label="Price"
  />
  <NumberInput
    {...args}
    defaultValue={12.5}
    min={undefined}
    max={undefined}
    locale="ar-EG"
    aria-label="Arabic digits"
  />
</Flex>

Snap To Step

const args: Partial<NumberInputProps> = {
  defaultValue: 7,
  min: 0,
  max: 100,
  step: 5,
  shouldSnapOnStep: true,
  incrementLabel: 'Increase',
  decrementLabel: 'Decrease',
  variant: 'outlined',
  'aria-label': 'Quantity',
}

<NumberInput key={String(args.defaultValue)} {...args} />

Scrub With The Wheel

const args: Partial<NumberInputProps> = {
  defaultValue: 5,
  min: 0,
  max: 10,
  shouldScrubOnWheel: true,
  incrementLabel: 'Increase',
  decrementLabel: 'Decrease',
  variant: 'outlined',
  'aria-label': 'Quantity',
}

<NumberInput key={String(args.defaultValue)} {...args} />

Variants

const args: Partial<NumberInputProps> = {
  'aria-label': 'Quantity',
  defaultValue: 5,
  min: 0,
  max: 10,
  incrementLabel: 'Increase',
  decrementLabel: 'Decrease',
  variant: 'outlined',
}

<Flex gap={2}>
  {numberInputVariantTokens.map((variant) => (
    <NumberInput
      key={variant}
      {...args}
      variant={variant}
      aria-label={`Quantity, ${variant}`}
    />
  ))}
</Flex>

Sizes

const args: Partial<NumberInputProps> = {
  'aria-label': 'Quantity',
  defaultValue: 5,
  min: 0,
  max: 10,
  incrementLabel: 'Increase',
  decrementLabel: 'Decrease',
  variant: 'outlined',
}

<Flex gap={2}>
  {(['sm', 'md', 'lg'] as const).map((size) => (
    <NumberInput key={size} {...args} size={size} aria-label={`Quantity, ${size}`} />
  ))}
</Flex>

States

const args: Partial<NumberInputProps> = {
  'aria-label': 'Quantity',
  defaultValue: 5,
  min: 0,
  max: 10,
  incrementLabel: 'Increase',
  decrementLabel: 'Decrease',
  variant: 'outlined',
}

<Flex gap={2}>
  <NumberInput {...args} error aria-label="Quantity, in error" />
  <NumberInput {...args} disabled aria-label="Quantity, disabled" />
  <NumberInput {...args} readOnly aria-label="Quantity, read-only" />
</Flex>

In Form Control

Between 1 and 99.

const { error, disabled, required, size, color, fullWidth }: Partial<NumberInputProps> = {
  'aria-label': 'Quantity',
  defaultValue: 5,
  min: 0,
  max: 10,
  incrementLabel: 'Increase',
  decrementLabel: 'Decrease',
  variant: 'outlined',
}

<FormControl
  error={error}
  disabled={disabled}
  required={required}
  size={size}
  color={color}
  fullWidth={fullWidth}
>
  <FormLabel>Quantity</FormLabel>
  <NumberInput
    variant="outlined"
    min={1}
    max={99}
    defaultValue={1}
    incrementLabel="Increase"
    decrementLabel="Decrease"
  />
  <FormHelperText>{error ? 'Choose between 1 and 99.' : 'Between 1 and 99.'}</FormHelperText>
</FormControl>

Right To Left

const args: Partial<NumberInputProps> = {
  defaultValue: 5,
  min: 0,
  max: 10,
  spinButtons: 'sides',
  incrementLabel: 'Increase',
  decrementLabel: 'Decrease',
  variant: 'outlined',
  'aria-label': 'Quantity',
}

<View dir="rtl">
  <NumberInput {...args} locale="ar-EG" />
</View>

Customized

<ThemeProvider theme={customized}>
  <NumberInput aria-label="Quantité" defaultValue={10} min={0} max={100} />
</ThemeProvider>