HTML `<input type="number">` element - Customize increment value of arrow buttons independently from `step`

Viewed 94

I have an HTML <input> element with type="number":

Number Input

I want to use a step value of 1. That is, any integer is allowed. However, I would also like the arrow buttons to increment/decrement the value of the input by some value, for example, 5. Is this possible?

From the MDN docs, I can't find a way to accomplish this using HTML alone. I would also accept an answer involving intercepting the input event using javascript to change the value of the input, but I don't know of a way to distinguish between a change due to keypresses vs. a change due to clicking the arrow buttons.

3 Answers

You can always capture the input in javascript and convert the values to an integer with parseInt and use the normal step to control the arrow stepping.

With this method you don't have to worry about distinguishing between the stepping and input as the steps will already be integers anyway.

let nums = document.querySelectorAll("input[type='number']");

nums.forEach(function(e){
  e.addEventListener("input",function(el){
    el.target.value = parseInt(el.target.value)    
  });
});
<input type="number" step="4" />

Currently it is not possible to capture a click on the spinners directly, but there are some ways to detect a click on them.

By using different events it is possible to adjust the step size for the moment. But it may cause some unwanted effect if the user uses the mouse to click on a spinner, keeps the mousekey pressed and presses an arrow key up/down:

// fires ones right before the value is changed for the first time
input.addEventListener("mousedown", ({ target }) => target.setAttribute("step", 5));
input.addEventListener("mouseup", ({ target }) => target.setAttribute("step", 1));
// fires after release of the mouse key, even if the user moved the mouse
input.addEventListener("change", ({ target }) => target.setAttribute("step", 1));
<input id="input" type="number" step="1" value="0" />

<label> Overlay

Make a <label> that lays over the spinner button of the <input> and in the eventhandler use the .stepUp() and .stepDown() methods when the <label> is clicked.

In Example A, the <label> is outlined in red and the two nested <b> are outlined in blue. The outlines are just for the demo of course and can be easily removed. See Example A

XY Coordinates

Another way is to place an abspos (absolute positioned) <input> in a relpos container and change the step attribute if the click is within the xy coords of the <input> spinner button. See Example B.

Details are commented in both Example A and B

Example A

<label> overlay

// Bind <label> to the click event
document.querySelector('.spinner').onclick = incDec;

function incDec(e) {
  // Reference the tag the user clicked
  const clk = e.target;
  // Reference the <input>
  const int = this.previousElementSibling;
  // If the user clicked .inc...
  if (clk.matches('.inc')) {
    //...Increment <input> value by 5
    int.stepUp(5);
  }
  // If the user clicked .dec...
  if (clk.matches('.dec')) {
    //...Decrement <input> value by 5
    int.stepDown(5);
  }
}
.spinner {
  position: relative;
  top: -4px;
  right: 24px;
  z-index: 1;
  display: inline-flex;
  flex-flow: column nowrap;
  justify-content: center;
  align-items: center;
  width: 16px;
  height: 18px;
  outline: 1px dashed red;
}

.spinner b {
  display: inline-block;
  width: 100%;
  height: 50%;
  outline: 1px blue solid
}
<input id='int' type='number' min='0' step='1'>
<label for='int' class='spinner'><b class='inc'></b><b class='dec'></b></label>

Example B

XY Coordinates

// Reference <form>
const UI = document.forms.UI;
// Reference all form controls
const IO = UI.elements;
// Reference <fieldset>
const set = IO.set;
// Reference <input>
const int = IO.int;
// Define an empty array to xy coords
let point = [];

// Bind <fieldset> to 'mousemove' event
set.onmousemove = xy;
// Bind <input> to 'mousedown' event
int.onmousedown = setPoint;
/*
Bind <input> to 'mouseup' event
restore step attribute back to 1
*/
int.onmouseup = e => e.target.step = 1;

/*
Assigns current xy coords within the
perimeter of <fieldset> to array point
*/
function xy(e) {
  let x = e.clientX;
  let y = e.clientY;
  point[0] = x;
  point[1] = y;
}

/*
Checks if the user is clicking the <input>
spinner. If user is, then step attribute
is changed to 5
*/
function setPoint(e) {
  let x = point[0];
  let y = point[1];
  if (x > 215 && x < 231 && y > 30 && y < 51) {
    this.step = 5;
  }
}
html {
  font: 300 62.5%/1 Consolas;
}

fieldset {
  position: relative;
  width: 24rem;
  height: 5rem;
}

input {
  position: absolute;
  left: 2.5rem;
  top: 1.85rem;
  width: 19rem;
  font: inherit;
  font-size: 1.6rem;
  text-align: center;
}
<form id='UI'>
  <fieldset id='set'>
    <input id='int' type='number' min='0' step='1'>
  </fieldset>
</form>

Related