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.
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.
<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.
<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 | component | The box around the field and the two buttons; rendered by the plugin, or written by hand for custom buttons |
number-input-sm | size | Smaller field and buttons |
number-input-lg | size | Larger 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>");
