Menu

Number input

Added in 1.7.0

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.

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.

<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.

<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.

<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, ↑ and ↓ step the value, Home and End 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.

<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.

<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.

<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.

In stock, ships today.
Only 5 pieces left.
<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.

Pick the number of seats.
<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.

<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:

initAll(SELECTOR_DATA_TOGGLE, NumberInput)

Create one manually, or get an existing instance:

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:

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.

Class reference

Every class this component ships, grouped by what it changes. A name in braces stands for a family — {color} is any base color, {breakpoint} any responsive step.

number-input componentThe box around the field and the two buttons; rendered by the plugin, or written by hand for custom buttons
number-input-sm sizeSmaller field and buttons
number-input-lg sizeLarger field and buttons

CSS

Variables

The component uses local CSS variables on .number-input for real-time customization. Values for the CSS variables are set via Sass, so Sass customization is still supported, too. Source: core/scss/ui/forms/_form-number-input.scss.

--number-input-input-width: #{$number-input-input-width};
--number-input-bg: #{$number-input-bg};
--number-input-border-color: #{$number-input-border-color};
--number-input-border-radius: #{$number-input-border-radius};

Sass variables

Override these variables before importing Tabler to change the defaults. Source: core/scss/_variables.scss.

$number-input-input-width: 3.5rem;
$number-input-input-widths: (
  'sm': 3rem,
  'lg': 4rem,
);
$number-input-icon-sizes: (
  'sm': $input-btn-icon-size-sm,
  'lg': $input-btn-icon-size-lg,
);
$number-input-bg: $input-bg;
$number-input-border-color: var(--border-color);
$number-input-border-radius: $input-border-radius;
$number-input-icon-decrement: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path fill='none' stroke='black' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M5 12h14'/></svg>");
$number-input-icon-increment: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path fill='none' stroke='black' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M12 5v14M5 12h14'/></svg>");