bootstrap-input-spinner

A Bootstrap 5 plugin to create input spinner elements for number input, by shaack.com engineering. Zero dependencies other than Bootstrap 5.

This version is compatible with Bootstrap 5. For Bootstrap 4 use the bootstrap4-compatible branch. npm versions 3.x are Bootstrap 5 compatible, versions 2.x Bootstrap 4 compatible.

License: MIT

Features

The Bootstrap InputSpinner

Usage

This script enables the InputSpinner for all inputs with type='number'. No extra css needed, just Bootstrap 5.

<script type="module">
    import {InputSpinner} from "./src/InputSpinner.js"
    for (const el of document.querySelectorAll("input[type='number']")) {
        new InputSpinner(el)
    }
</script>

Repository, documentation and npm package

Find the source code, more documentation and the npm package at

Examples

The following contains examples of the InputSpinner's main features

No attributes

<input type="number"/>

Simple Integer

<input type="number" value="500" min="0" max="1000" step="10"/>

Floating Point

<input type="number" value="4.5" data-decimals="2" min="0" max="9" step="0.1"/>

Handle change and input events and read the value from JavaScript

Type in a number to see the difference between change and input events.

Value on input:
Value on change:

const changedInput = document.getElementById("changedInput")
changedInput.addEventListener("input", function (event) {
    document.getElementById("valueOnInput").textContent = event.target.value
})
changedInput.addEventListener("change", function (event) {
    document.getElementById("valueOnChange").textContent = event.target.value
})

Programmatic changing the value

inputNet.addEventListener("input", function (event) {
    inputGross.setValue(event.target.value * 1.19)
})
inputGross.addEventListener("input", function (event) {
    inputNet.setValue(event.target.value / 1.19)
})

Attributes placeholder and required

<input placeholder="Enter a number" required type="number" value="" min="-100" max="100"/>

Attribute disabled, dynamically changing

Attributes are handled dynamically.

<input id="inputDisabled" disabled type="number" value="50"/>
<div class="form-check">
    <input type="checkbox" checked class="form-check-input" id="disabledSwitch"/>
    <label class="form-check-label" for="disabledSwitch">Disabled</label>
</div>
<script>
    document.getElementById("disabledSwitch").addEventListener("change", function (event) {
        document.getElementById("inputDisabled").disabled = event.target.checked
    })
</script>

buttonsOnly mode and disabled autoInterval

In buttonsOnly mode no direct text input is allowed, the text-input gets the attribute readonly. But the plus and minus buttons still allow to change the value.
autoInterval: undefined additionally disables the auto increase/decrease, when you hold the button.

new InputSpinner(element, {buttonsOnly: true, autoInterval: undefined})

Mouse wheel stepping

Off by default, matching what modern browsers do with the native <input type="number">. Pass mouseWheel: true to enable it. The listener is attached only while the input has focus, so an unfocused spinner never hijacks page scroll.

new InputSpinner(element, {mouseWheel: true})

Dynamically handling of the class attribute

Try to change the class to "is-invalid" or "text-info".

<input id="inputChangeClass" class="is-valid" type="number" value="50"/>
<label for="classInput">CSS Class</label>
<input id="classInput" type="text" class="form-control" value="is-valid"/>
<script>
    document.getElementById("classInput").addEventListener("input", function (event) {
        document.getElementById("inputChangeClass").className = event.target.value
    })
</script>

Sizing

Sizing works out of the box. Just set the original inputs class to form-control-sm or form-control-lg, and the resulting group gets the class input-group-sm or input-group-lg.

<input class="form-control-sm" type="number" value="0.0" data-decimals="4" min="-1" max="1" step="0.0001"/>

<input class="form-control-lg" type="number" value="1000000" data-decimals="0" min="0" max="2000000" step="1"/>

Dynamically handling of min, max, step and data-decimals

const tester = document.getElementById("minMaxTester")
document.getElementById("minInput").addEventListener("change", function (event) {
    tester.setAttribute("min", event.target.value)
})
// ...same for max, step, data-decimals

Prefix and Suffix

<input data-prefix="$" value="100.0" data-decimals="2" min="0" max="1000" step="0.1" type="number" />

<input data-suffix="°C" value="50" min="0" max="100" type="number" />

Looping the value

This input starts from 0 when reaching 360.

<input step="10" type="number" id="inputLoop" value="0" data-decimals="0" min="-10" max="360"/>

"Loop" the value between 0 and 360 with the input event in JavaScript.

const inputLoop = document.getElementById("inputLoop")
inputLoop.addEventListener("input", function () {
    let value = parseInt(inputLoop.value, 10)
    value = (value < 0) ? 360 + value : value % 360
    inputLoop.setValue(value)
})

Custom Editors

An Editor defines how the input is parsed and rendered. The inputSpinner ships some custom Editors in /src/custom-editors.js, available as named ES exports.

RawEditor

The simplest custom Editor is the RawEditor, it renders just the value and parses just the value, without any changes, like a native number input. No internationalization, no digit grouping.

import {InputSpinner} from "./src/InputSpinner.js"
import {RawEditor} from "./src/custom-editors.js"
new InputSpinner(document.getElementById("rawEditor"), {editor: RawEditor})

TimeEditor

The TimeEditor renders the number as time in hours and minutes, separated by a colon.

value:
import {InputSpinner} from "./src/InputSpinner.js"
import {TimeEditor} from "./src/custom-editors.js"
new InputSpinner(document.getElementById("timeEditor"), {editor: TimeEditor})

Styling with templates

With the templating feature, you can almost do anything, when it comes to layout.

How about... buttons right

This is the template for "buttons right":

<div class="input-group ${groupClass}">
<input type="text" inputmode="decimal" style="text-align: ${textAlign}" class="form-control"/>
<button style="min-width: ${buttonsWidth}" class="btn btn-decrement ${buttonsClass}" type="button">${decrementButton}</button>
<button style="min-width: ${buttonsWidth}" class="btn btn-increment ${buttonsClass}" type="button">${incrementButton}</button>
</div>

You can... or must use the following variables in your template:

Provide the template as configuration parameter:

new InputSpinner(element, {template: '<div class...'})

Destroying the spinner

To remove the InputSpinner and show the original input element, use

element.destroyInputSpinner()

More Bootstrap components (from shaack.com)

You may want to check out our further Bootstrap extensions, bootstrap-show-modal and bootstrap-detect-breakpoint.

If you find bugs or have suggestions, you may write an issue.

Run the unit tests in your browser.