← Back toHTML DOM

Build a spin input

Written byPhuoc Nguyen
Created
18 Sep, 2023
Category
Level 2 — Intermediate
When users need to adjust a numerical value up or down, we often provide a user interface element to make it easy for them. That's where the spin input comes in handy.
HTML has a built-in input element for creating a spin input by setting the type attribute to `number`. This input only allows digits, which prevents users from entering invalid characters. When you click or tap on the input, two arrow buttons appear on the right side to increase or decrease the value.
Give it a try below:
Unfortunately, we can't easily customize the buttons, such as replacing them with our own images. But don't worry, in this post, we'll learn how to build a spin input with JavaScript DOM.

HTML Markup

To create the spin input, we'll need to start with the HTML markup. The layout includes an input element and two buttons for increasing and decreasing the value.
html
<div class="spin" id="spin">
<input type="text" name="input" class="spin__input" value="50">
<button class="spin__btn spin__btn--minus"></button>
<button class="spin__btn spin__btn--plus"></button>
</div>
We've created a container `div` with the class `spin`. Inside the container, there are two buttons with the classes `spin__btn spin__btn--minus` and `spin__btn spin__btn--plus`. These buttons will decrease and increase the value, respectively. Additionally, there's an `input` element with a default value of 50.
Instead of using the `number` input type, we're using the usual `text` attribute.

Basic styles

When creating buttons that require an up or down arrow, we have a few options. We can use a minus or plus sign, an image, or - as we'll explore in this post - fake triangles created with pure CSS.
To create a CSS triangle to represent the up and down arrow in buttons, we can use the `::before` or `::after` pseudo-element and some creative CSS. Here's an example of how to create an upward triangle:
css
.spin__btn--plus::before {
content: '';
width: 0;
height: 0;

border-left: 0.5rem solid transparent;
border-right: 0.5rem solid transparent;
border-bottom: 0.5rem solid rgb(203 213 225);
}
In this code snippet, we're adding a triangle before the `spin__btn--plus` class for styling purposes. The pseudo-element doesn't actually contain any content because we set the `content` property to an empty string.
To make the triangle invisible by default, we set the `width` and `height` properties to zero. Then, we use borders with varying colors and thicknesses to create the triangular shape. By setting the left and right borders to `transparent`, only the bottom border is visible. Finally, we set the color of the triangle with the `border-bottom-color` property.
We can use similar code with different border property values to create a downward-pointing triangle for our other button. Check out how the minus button could look with this styling:
css
.spin__btn--minus::before {
content: '';
width: 0;
height: 0;

border-left: 0.5rem solid transparent;
border-right: 0.5rem solid transparent;
border-top: 0.5rem solid rgb(203 213 225);
}

Positioning the buttons

To position our buttons perfectly within the container, we'll need to set the position property of the container to `relative`. This creates a positioning context for its child elements.
css
.spin {
position: relative;
}
With this setup, we can use absolute positioning to place our buttons within the container. We set their position properties to `absolute` and specify their `top` and `bottom` values.
css
.spin__btn {
position: absolute;
position: absolute;
right: 0;
height: 50%;
}
.spin__btn--minus {
top: 0;
}
.spin__btn--plus {
bottom: 0;
}
In the code snippet, we target all `.spin__btn` elements and set their `position` property to `absolute`. Then, we target each individual button and set its `top` or `bottom` properties depending on its desired position.
With these styles, our input element will be perfectly centered vertically within the container with our two arrow buttons positioned at either end.

Increasing and decreasing value

Now, we need to make our buttons functional by adding event listeners that update the value of our input element. We can do this easily with the `addEventListener` method.
js
const container = document.getElementById('spin');
const inputEle = container.querySelector('.spin__input');

const minusBtn = container.querySelector('.spin__btn--minus');
const plusBtn = container.querySelector('.spin__btn--plus');

const increase = () => {
const currentValue = inputEle.value;
const newValue = (currentValue === '') ? 0 : parseInt(currentValue, 10) + 1;
inputEle.value = newValue;
};

const decrease = () => {
const currentValue = inputEle.value;
const newValue = (currentValue === '') ? 0 : parseInt(currentValue, 10) - 1;
inputEle.value = newValue;
};

minusBtn.addEventListener('click', decrease);
plusBtn.addEventListener('click', increase);
First, we select the input element and both buttons using `document.getElementById` and `document.querySelector`. Then, we add event listeners to the minus and plus buttons. These listeners call the `decrease` and `increase` methods on the input element, respectively, which decrease or increase the value by 1. It's that simple!

Accepting digits only

Previously, we used the `number` type for input, which prevented users from entering invalid characters. However, we've changed our approach and now need to restrict input to digits only.
To achieve this, we can handle the `keydown` event, which fires when a user presses a key. We can then verify whether the pressed key is a number by comparing it with the `^[0-9]+$` pattern.
If the key is not a number, we prevent the default behavior by calling `preventDefault()`. It's worth noting that we still allow users to use the Backspace and Delete keys to remove digits as normal.
Here's how we handle the `keydown` event:
js
const handleKeyDown = (e) => {
if (!/^[0-9]+$/.test(e.key) && e.key !== 'Backspace' && e.key !== 'Delete') {
e.preventDefault();
}
};

inputEle.addEventListener('keydown', handleKeyDown);

Enhancing user experience with keyboard shortcuts

In addition to clicking buttons, we can further improve the user experience by allowing them to adjust the value using keyboard shortcuts. For example, pressing the up arrow key can increase the value, while pressing the down arrow key can decrease it.
To enable this feature, we need to update the `keydown` event handler to detect if the user presses the left or right arrow keys and adjust the value accordingly.
Here's an updated version of our code that includes keyboard shortcuts to enhance the user experience.
js
const handleKeyDown = (e) => {
switch (e.key) {
case 'ArrowDown':
decrease();
break;
case 'ArrowUp':
increase();
break;
default:
if (!/^[0-9]+$/.test(e.key) && e.key !== 'Backspace' && e.key !== 'Delete') {
e.preventDefault();
}
break;
}
};

Setting limits on input values

When working with input values, it's often necessary to set a minimum and maximum range for possible values. This can easily be done using the `min` and `max` attributes.
html
<input min="0" max="100">
To ensure that the input value falls within the desired range, we can update our `increase` and `decrease` functions to include the `clamp` function. The `clamp` function is a utility function that ensures a value falls within a given range. It takes three arguments: `min`, `max`, and `value`. The function checks if `value` is less than `min`, and returns `min` if it is. If `value` is greater than `max`, the function returns `max`. If `value` is within the range of `min` and `max`, the function returns `value`.
js
const clamp = (min, max, value) => Math.min(Math.max(value, min), max);
With this in mind, we can update our code to include the `clamp` function in our `increase` and `decrease` functions. This ensures that the input value falls within the specified range.
By setting limits on input values, we can ensure that our code is more robust and less prone to errors.
js
const min = parseInt(target.getAttribute('min'), 10);
const max = parseInt(target.getAttribute('max'), 10);

const increase = () => {
const currentValue = inputEle.value;
const newValue = (currentValue === '') ? 0 : parseInt(currentValue, 10) + 1;
inputEle.value = clamp(min, max, newValue);
};

const decrease = () => {
const currentValue = inputEle.value;
const newValue = (currentValue === '') ? 0 : parseInt(currentValue, 10) - 1;
inputEle.value = clamp(min, max, newValue);
};
And that's all! With only a few lines of JavaScript and CSS, we've created a spin input that allows you to adjust a numerical value up or down. Give it a try and see for yourself by playing around with the demo below.

See also

If you found this post helpful, please consider giving the repository a star on GitHub or sharing the post on your favorite social networks 😍. Your support would mean a lot to me!

Questions? 🙋

Do you have any questions about front-end development? If so, feel free to create a new issue on GitHub using the button below. I'm happy to help with any topic you'd like to learn more about, even beyond what's covered in this post.
While I have a long list of upcoming topics, I'm always eager to prioritize your questions and ideas for future content. Let's learn and grow together! Sharing knowledge is the best way to elevate ourselves 🥷.
Ask me questions

Recent posts ⚡

Newsletter 🔔

If you're into front-end technologies and you want to see more of the content I'm creating, then you might want to consider subscribing to my newsletter.
By subscribing, you'll be the first to know about new articles, products, and exclusive promotions.
Don't worry, I won't spam you. And if you ever change your mind, you can unsubscribe at any time.
Phước Nguyễn