Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

The stepper may also be known as a spinner or spin button.

...

Usage & Behavior

General Guidelines

...

  • Label - Where relevant, the preceding label should indicate the units of the value.

...

  • Intervals - The interval between each up/down click may vary according to the content type and the task.

    • By default, the interval should be 1 unit per button press.

    • In some cases, other intervals are more appropriate, depending on the task. For example, speed may be in intervals of 10 units per click (e.g. 60 kph - 70 kph - 80 kph).

    • Manually entering a value in the number field which is in between the defined intervals may be valid or invalid.

      • A valid value will be set.

      • An invalid value may be rounded up to the nearest valid value or ignored. (See Validations and Errors below).

  • Minimum and maximum values - When the stepper reaches the minimum or maximum value, the up/down buttons will be disabled respectively.

...

  • Long press on up/down buttons - will enable faster continuous increment or decrement.

States

State

Image

Comment

Regular

Image Modified

Hover

Image ModifiedImage ModifiedImage Modified

Active

Image ModifiedImage ModifiedImage Modified

Disabled

Image Modified

Read-Only

Numeric Stepper.pngImage Added

Error

Image Modified

Warning

Image Removed
Numeric Stepper - Warning.pngImage Added

Focused

Image Modified

Focused, Hover

Image ModifiedImage ModifiedImage Modified

Focused, Active

Image ModifiedImage ModifiedImage Modified

Focused, Disabled

Image Modified

Interaction

  • Clicking the up/down buttons will increase or decrease the value by the defined interval:

Image ModifiedImage Modified
  • When the number field is empty:

    • clicking the up button will populate the field with a value one interval above the minimum.

    • clicking the down button will populate the field with the minimum value.

  • Focusing on the number field using the mouse pointer, a text cursor will appear in the position the user clicks:

Image Modified
  • Focusing on the number field using Tab, the whole number will be selected to allow quick entry of a new value:

Image Modified
  • While focused on the number field, the up/down keys on the keyboard will increase/decrease the value. The number field will remain focused and the text cursor will remain in place.

  • The user may enter a numeric value using the keyboard.

  • When the numeric stepper is selected, the user can use the mouse wheel to increase/decrease the value.

  • Long press on up/down buttons will enable faster continuous increment or decrement.

  • Pressing the Tab key or clicking outside of the control will move the focus away from the stepper and set the defined value (or the last valid value).

Validations and Errors

...

  • When the stepper reaches the minimum or maximum value, the up/down buttons will be disabled respectively.

  • Other validations will be provided for invalid syntax, invalid numbers, and missing values. See Field Validation for more info.

...

  • In the focus state, typing will enter a value

Keyboard

Description

Tab

Navigates to the next component.

Shift + tab

Navigates to the previous component.

Space

N/A

Enter

N/A

Esc

N/A

Arrows

  • Up/ Down - Increases or decreases the value accordingly.

  • Holding down the key will scroll continuously.

Home

Sets slider to its minimum value.

End

Sets slider to its maximum value.

Mouse

Date picker menu

Right click on numeric field

Set Focus on component

Design

Zeplin link

https://zpl.io/2p9kWgl

Image Removed
Numeric Stepper - States.pngImage Added

Code

Html macro
sanitizefalse
<link rel="stylesheet" href="https://ux.verint.com/bootstrap-4.0.0/dist/fonts/css/verint_lux.css">
<link rel="stylesheet" href="https://ux.verint.com/bootstrap-4.0.0/dist/css/bootstrap.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://ux.verint.com/bootstrap-4.0.0/dist/js/bootstrap.bundle.js"></script>
<script src="https://ux.verint.com/bootstrap-4.0.0/dist/other/accessibility.js"></script>
<script src="https://ux.verint.com/bootstrap-4.0.0/dist/other/prism.min.js"></script>
<link rel="stylesheet" href="https://ux.verint.com/bootstrap-4.0.0/dist/other/prism-coy.min.css">
			

<div class="card">
			<div class="card-header">Numeric Stepper <button id="toggleMarkup" type="button" class="btn btn-sm btn-outline-primary btn btn-fixed-width float-right m-0" onclick="$('pre' ).toggle()">Toggle Markup</button></div>
			<div class="card-body">
				<form>
					<div class="form-group row">
						<label for="inputKey" class="col-lg-2 col-form-label col-form-label-sm">Regular</label>
						<div class="col-lg-4">
							<div class="form-control-container">
								<input type="number" class="form-control form-control-sm" placeholder="Enter a number" min="0" max="100" value="0">
								<div class="btn-group btn-group-sm spinner-group">
								  <button type="button" class="btn btn-outline-light btn-sm increment" tabindex="-1">
								    <i class="icon-up"></i>
								  </button>
								  <button type="button" class="btn btn-outline-light btn-sm decrement" tabindex="-1">
								    <i class="icon-down"></i>
								  </button>
								</div>
							</div>
						</div>
						<div class="col-lg-6">
							<pre class="language-html"><code><script type="prism-html-markup"><div class="form-control-container">
	<input type="number" class="form-control form-control-sm" placeholder="Enter a number" min="0" max="100" value="0">
	<div class="btn-group btn-group-sm spinner-group">
		<button type="button" class="btn btn-outline-light btn-sm increment" tabindex="-1">
			<i class="icon-up"></i>
		</button>
		<button type="button" class="btn btn-outline-light btn-sm decrement" tabindex="-1">
			<i class="icon-down"></i>
		</button>
	</div>
</div></script></code></pre>
						</div>
					</div>
					<div class="form-group row">
						<label for="inputKey" class="col-lg-2 col-form-label col-form-label-sm">Disabled</label>
						<div class="col-lg-4">
							<div class="form-control-container">
								<input type="number" class="form-control form-control-sm" placeholder="Enter a number" min="0" max="100" value="0" disabled>
								<div class="btn-group btn-group-sm spinner-group">
								  <button type="button" class="btn btn-outline-light btn-sm increment" tabindex="-1" disabled>
								    <i class="icon-up"></i>
								  </button>
								  <button type="button" class="btn btn-outline-light btn-sm decrement" tabindex="-1" disabled>
								    <i class="icon-down"></i>
								  </button>
								</div>
							</div>
						</div>
						<div class="col-lg-6">
							<pre class="language-html"><code><script type="prism-html-markup"><div class="form-control-container">
	<input type="number" class="form-control form-control-sm" placeholder="Enter a number" min="0" max="100" value="0" disabled>
	<div class="btn-group btn-group-sm spinner-group">
		<button type="button" class="btn btn-outline-light btn-sm increment" tabindex="-1" disabled>
			<i class="icon-up"></i>
		</button>
		<button type="button" class="btn btn-outline-light btn-sm decrement" tabindex="-1" disabled>
			<i class="icon-down"></i>
		</button>
	</div>
</div></script></code></pre>
						</div>
					</div>
					<div class="form-group row">
						<label for="inputKey" class="col-lg-2 col-form-label col-form-label-sm">Read-Only</label>
						<div class="col-lg-4">
							<div class="form-control-container">
								<input type="number" class="form-control form-control-sm" placeholder="Enter a number" min="0" max="100" value="0" readonly>
								<div class="btn-group btn-group-sm spinner-group">
								  <button type="button" class="btn btn-outline-light btn-sm increment" tabindex="-1" disabled>
								    <i class="icon-up"></i>
								  </button>
								  <button type="button" class="btn btn-outline-light btn-sm decrement" tabindex="-1" disabled>
								    <i class="icon-down"></i>
								  </button>
								</div>
							</div>
						</div>
						<div class="col-lg-6">
							<pre class="language-html"><code><script type="prism-html-markup"><div class="form-control-container">
	<input type="number" class="form-control form-control-sm" placeholder="Enter a number" min="0" max="100" value="0" readonly>
	<div class="btn-group btn-group-sm spinner-group">
		<button type="button" class="btn btn-outline-light btn-sm increment" tabindex="-1" disabled>
			<i class="icon-up"></i>
		</button>
		<button type="button" class="btn btn-outline-light btn-sm decrement" tabindex="-1" disabled>
			<i class="icon-down"></i>
		</button>
	</div>
</div></script></code></pre>
						</div>
					</div>
					<div class="form-group row">
						<label for="inputKey" class="col-lg-2 col-form-label col-form-label-sm">Warning</label>
						<div class="col-lg-4">
							<div class="form-control-container">
								<input type="number" class="form-control form-control-sm is-warning" placeholder="Enter a number" min="0" max="100" value="0">
								<div class="btn-group btn-group-sm spinner-group">
								  <button type="button" class="btn btn-outline-light btn-sm is-warning increment" tabindex="-1">
								    <i class="icon-up"></i>
								  </button>
								  <button type="button" class="btn btn-outline-light btn-sm is-warning decrement" tabindex="-1">
								    <i class="icon-down"></i>
								  </button>
								</div>
							</div>
						</div>
						<div class="col-lg-6">
							<pre class="language-html"><code><script type="prism-html-markup"><div class="form-control-container">
	<input type="number" class="form-control form-control-sm is-warning" placeholder="Enter a number" min="0" max="100" value="0">
	<div class="btn-group btn-group-sm spinner-group">
		<button type="button" class="btn btn-outline-light btn-sm is-warning increment" tabindex="-1">
			<i class="icon-up"></i>
		</button>
		<button type="button" class="btn btn-outline-light btn-sm is-warning decrement" tabindex="-1">
			<i class="icon-down"></i>
		</button>
	</div>
</div></script></code></pre>
						</div>
					</div>
					<div class="form-group row">
						<label for="inputKey" class="col-lg-2 col-form-label col-form-label-sm">Error</label>
						<div class="col-lg-4">
							<div class="form-control-container">
								<input type="number" class="form-control form-control-sm is-invalid" placeholder="Enter a number" min="0" max="100" value="101">
								<div class="btn-group btn-group-sm spinner-group">
								  <button type="button" class="btn btn-outline-light btn-sm is-invalid increment" tabindex="-1">
								    <i class="icon-up"></i>
								  </button>
								  <button type="button" class="btn btn-outline-light btn-sm is-invalid decrement" tabindex="-1">
								    <i class="icon-down"></i>
								  </button>
								</div>
							</div>
						</div>
						<div class="col-lg-6">
							<pre class="language-html"><code><script type="prism-html-markup"><div class="form-control-container">
	<input type="number" class="form-control form-control-sm is-invalid" placeholder="Enter a number" min="0" max="100" value="101">
	<div class="btn-group btn-group-sm spinner-group">
		<button type="button" class="btn btn-outline-light btn-sm is-invalid increment" tabindex="-1">
			<i class="icon-up"></i>
		</button>
		<button type="button" class="btn btn-outline-light btn-sm is-invalid decrement" tabindex="-1">
			<i class="icon-down"></i>
		</button>
	</div>
</div></script></code></pre>
						</div>
					</div>
				</form>
			</div>
		</div>



<script>
// Spinner
		jQuery.propHooks.disabled = {
		  set: function (el, value) {
		    if (el.disabled !== value) {
		      el.disabled = value;
		      value && $(el).trigger('disabledSet');
		      !value && $(el).trigger('enabledSet');
		    }
		  }
		};
		
		$('.form-control[type="number"]').on('keypress', function(e){
		  return e.metaKey || // cmd/ctrl
		    e.which <= 0 || // arrow keys
		    e.which == 8 || // delete key
		    /[0-9]/.test(String.fromCharCode(e.which)); // numbers
		});
		
		$('.form-control[type="number"]').on('input', function(e){
		  	if(!($(this).is(':disabled') || $(this).is('[readonly]'))) {
		        let disableMinButton = (Number(this.value)) <= Math.abs(this.getAttribute('min'));
		      	$(this).siblings('.spinner-group').find('.decrement').prop("disabled", disableMinButton);
		      	
		      	let disableMaxButton = (Number(this.value)) >= Math.abs(this.getAttribute('max'));
		      	$(this).siblings('.spinner-group').find('.increment').prop("disabled", disableMaxButton);
	        }

		  	$(this).toggleClass('is-invalid', $(this).is(':invalid'));
		  	$(this).siblings('.spinner-group').find('button').toggleClass('is-invalid', $(this).is(':invalid'));
		});
		
		$('.form-control[type="number"]').trigger("input"); // update bars at startup
			
		function incr(elem) {
			stepper = $(elem).closest('.form-control-container').find('.form-control[type="number"]');
			stepper.get(0).stepUp(1);
			stepper.trigger("input");
		}
		
		function decr(elem) {
			stepper = $(elem).closest('.form-control-container').find('.form-control[type="number"]');
			stepper.get(0).stepDown(1);
			stepper.trigger("input");
		}
		
		var increment = $(".spinner-group .increment");
		
		increment.on( "click", function() {
			incr(this);
		});
		
		var decrement = $(".spinner-group .decrement");
		
		decrement.on( "click", function() {
			decr(this);
		});
		
		var incrTimeoutId = 0;
		
		$(increment).on('mousedown', function() {
			incrTimeoutId = setTimeout(() => {incrTimeoutId = setInterval(() => {incr(this);}, 40);}, 250);
		}).on('mouseup mouseleave disabledSet', function() {
		    clearTimeout(incrTimeoutId);
		});
		
		var decrTimeoutId = 0;
		
		$(decrement).on('mousedown', function() {
			decrTimeoutId = setTimeout(() => {decrTimeoutId = setInterval(() => {decr(this);}, 40);}, 250);
		}).on('mouseup mouseleave disabledSet', function() {
		    clearTimeout(decrTimeoutId);
		});
</script>

...