# Number input

> A number field with a decrease and an increase button, for a quantity in a cart, a number of seats or any value changed in fixed steps.

Add a number input with min, max and step, hold-to-repeat buttons, arrow key support and change events.

Source: https://docs.tabler.io/ui/forms/number-input

---

## Overview

A number input is an ordinary `<input>` between two buttons. The input stays the only field a form sees: the buttons, the arrow keys and the JavaScript API all write to it, keep the value between `min` and `max` and round it to the precision of `step`.

Add `data-bs-toggle="number-input"` to the input and Tabler does the rest: it wraps the field in a `.number-input` box and renders the two buttons. The bounds come from the input's own `min`, `max` and `step` attributes.

```html
<input type="text" class="form-control" value="1" min="1" max="99" aria-label="Quantity" data-bs-toggle="number-input" />
```

## Usage

### Bounds and step

The button at a bound goes disabled, so the user sees when the value cannot go further. Letters never get into the field: a typed one is dropped and a pasted text keeps only its digits, with a minus sign allowed only when `min` is negative and a decimal separator only for a fractional `step`. A typed value is clamped
once the field is left. A fractional `step` sets the precision: with `step="0.25"` the value is always shown with two decimals.

```html
<input type="text" class="form-control" value="1.5" min="0" max="10" step="0.25" aria-label="Hours" data-bs-toggle="number-input" />
```

Without `min` and `max` the value has no bounds. An empty field steps from `min` when that is positive, otherwise from zero.

### Text or number input

A `type="text"` input gets `role="spinbutton"`, the matching `aria-value*` attributes and a numeric `inputmode`, so a phone shows the number keyboard and the native spin buttons never appear. A `type="number"` input keeps the browser's own validation and keyboard behaviour; the number input only hides its spin buttons.

```html
<input type="number" class="form-control" value="5" min="0" max="10" aria-label="Seats" data-bs-toggle="number-input" />
```

### Keyboard

With the focus in the field, <kbd>↑</kbd> and <kbd>↓</kbd> step the value, <kbd>Home</kbd> and <kbd>End</kbd> jump to `min` and `max`. Holding a button down keeps stepping after a short delay; set `data-bs-repeat="false"` to turn that off.

### Sizes

Add `.form-control-sm` or `.form-control-lg` to the input; the rendered box gets `.number-input-sm` or `.number-input-lg`, so the field and the buttons keep one height.

```html
<input type="text" class="form-control form-control-sm" value="1" min="0" aria-label="Small" data-bs-toggle="number-input" />
<div class="number-input" data-bs-toggle="number-input">
  <button type="button" class="btn btn-icon" data-bs-number-input-action="decrement" aria-label="Decrease"></button>
  <input type="text" class="form-control" value="1" min="0" aria-label="Default" />
  <button type="button" class="btn btn-icon" data-bs-number-input-action="increment" aria-label="Increase"></button>
</div>
<input type="text" class="form-control form-control-lg" value="1" min="0" aria-label="Large" data-bs-toggle="number-input" />
```

### Wider input

The input is `3.5rem` wide by default, enough for a quantity. Set `--tblr-number-input-input-width` on the input for longer values.

```html
<input type="text" class="form-control" value="1000" min="0" step="100" aria-label="Amount" data-bs-toggle="number-input" style="--tblr-number-input-input-width: 6rem" />
```

### Disabled

Add `disabled` or `readonly` to the input; both buttons follow.

```html
<input type="text" class="form-control" value="3" disabled aria-label="Quantity" data-bs-toggle="number-input" />
```

### Validation

Add `.is-valid` or `.is-invalid` to the input, the same way you would on any `.form-control`. The field is too narrow for the validation icon, so only the border changes colour. A `.valid-feedback` or `.invalid-feedback` placed right after the input still shows, even though the plugin puts its box between them.

```html
<div>
  <input type="text" class="form-control is-valid" value="2" min="1" max="5" aria-label="Quantity" data-bs-toggle="number-input" />
  <div class="valid-feedback">In stock, ships today.</div>
</div>
<div>
  <input type="text" class="form-control is-invalid" value="5" min="1" max="5" aria-label="Quantity" data-bs-toggle="number-input" />
  <div class="invalid-feedback">Only 5 pieces left.</div>
</div>
```

With a `type="number"` input the browser validates `required` itself (the plugin keeps the value inside `min` and `max` anyway), so a `.was-validated` form colours the box without any class on the input.

```html
<form class="was-validated" novalidate>
  <div class="mb-3">
    <label class="form-label" for="number-input-seats">Seats</label>
    <input type="number" class="form-control" id="number-input-seats" value min="1" max="10" required data-bs-toggle="number-input" />
    <div class="invalid-feedback">Pick the number of seats.</div>
  </div>
</form>
```

### Custom buttons

To change the icons, the labels or the classes of the buttons, write the markup yourself: put `data-bs-toggle="number-input"` on a `.number-input` wrapper and mark the buttons with `data-bs-number-input-action="decrement"` and `"increment"`. An empty button still gets the default icon from CSS; any content inside replaces it.

```html
<div class="number-input" data-bs-toggle="number-input">
  <button type="button" class="btn btn-icon" data-bs-number-input-action="decrement" aria-label="Fewer">
    <!-- Download SVG icon from http://tabler.io/icons/icon/chevron-down -->
    <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false" class="icon">
      <path d="M6 9l6 6l6 -6" />
    </svg>
  </button>
  <input type="text" class="form-control" value="1" min="1" max="99" aria-label="Quantity" />
  <button type="button" class="btn btn-icon" data-bs-number-input-action="increment" aria-label="More">
    <!-- Download SVG icon from http://tabler.io/icons/icon/chevron-up -->
    <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false" class="icon">
      <path d="M6 15l6 -6l6 6" />
    </svg>
  </button>
</div>
```

## Options

Options are read from `data-bs-*` attributes on the element that carries `data-bs-toggle` (the `data-tblr-*` alias also works), or passed to the constructor. An option set this way wins over the input's attribute.

| Option           | Type           | Default        | Description                                                                                          |
| ---------------- | -------------- | -------------- | ---------------------------------------------------------------------------------------------------- |
| `min`            | number or null | `null`         | Lower bound. When `null`, the input's `min` attribute is used; without that there is no lower bound. |
| `max`            | number or null | `null`         | Upper bound. When `null`, the input's `max` attribute is used; without that there is no upper bound. |
| `step`           | number or null | `null`         | Amount per step. When `null`, the input's `step` attribute is used, or `1`.                          |
| `repeat`         | boolean        | `true`         | Keeps stepping while a button is held down.                                                          |
| `repeatDelay`    | number         | `400`          | Milliseconds a button has to be held before it starts repeating.                                     |
| `repeatInterval` | number         | `80`           | Milliseconds between two repeated steps.                                                             |
| `decrementLabel` | string         | `\'Decrease\'` | `aria-label` of the rendered decrease button.                                                        |
| `incrementLabel` | string         | `\'Increase\'` | `aria-label` of the rendered increase button.                                                        |

## JavaScript

Tabler creates a `NumberInput` for every element matching `[data-bs-toggle="number-input"]` (or `[data-tblr-toggle="number-input"]`), an input or a `.number-input` wrapper:

```ts
initAll(SELECTOR_DATA_TOGGLE, NumberInput)
```

_Source: `core/js/src/number-input.ts`_

Create one manually, or get an existing instance:

```javascript
const el = document.querySelector('#quantity')
const numberInput = tabler.NumberInput.getOrCreateInstance(el, { min: 1, max: 10 })
```

The input itself fires the usual `input` and `change` events whenever a button or the API changes it, so a form, a framework binding or validation sees a stepped value like a typed one. On top of that, `change.bs.numberInput` fires on the element the plugin was created on with both values:

```javascript
el.addEventListener('change.bs.numberInput', (event) => {
  console.log(event.value, event.previousValue)
})
```

### Methods

| Method            | Description                                                              |
| ----------------- | ------------------------------------------------------------------------ |
| `getValue()`      | Returns the current value as a number, or `null` for an empty field.     |
| `setValue(value)` | Sets the value, clamped to the bounds and rounded to the step precision. |
| `increment()`     | Adds one step.                                                           |
| `decrement()`     | Removes one step.                                                        |
| `dispose()`       | Removes the listeners and the instance.                                  |
