@use 'sprucecss/scss/spruce' as *;
@include generate-form-control(
'.number-group',
false,
false,
false
);
.number-group {
align-items: center;
display: inline-flex;
gap: spacer('xxs');
inline-size: auto;
max-inline-size: 8rem;
padding: 0.25em;
position: relative;
&:focus-within {
@include short-ring('input');
}
&__input {
appearance: textfield;
background-color: transparent;
border: 0;
color: color('text', 'form');
font-size: config('font-size', $form-control);
font-variant-numeric: tabular-nums;
font-weight: config('font-weight', $form-control);
inline-size: 100%;
line-height: config('line-height', $form-control);
min-inline-size: 2ch;
outline: 0;
padding: 0;
text-align: center;
&::-webkit-outer-spin-button,
&::-webkit-inner-spin-button {
appearance: none;
margin: 0;
}
}
&__decrement,
&__increment {
border-radius: config('border-radius', $form-control-sm, false);
flex-shrink: 0;
&:disabled {
cursor: not-allowed;
}
}
}
<div class="form-group">
<label class="form-label" for="quantity">Quantity</label>
<div class="number-group" data-number-group>
<button class="btn btn--primary btn--sm btn--icon number-group__decrement" type="button" aria-controls="quantity" data-step="-1">
<span class="sr-only">Decrease quantity</span>
<svg class="btn__icon" aria-hidden="true" fill="none" focusable="false" stroke-linecap="round" stroke-linejoin="round" stroke-width="3.5" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M5 12h14"/></svg>
</button>
<input class="number-group__input" id="quantity" type="number" inputmode="numeric" name="quantity" value="1" min="1" max="10" step="1" aria-describedby="quantity-help">
<button class="btn btn--primary btn--sm btn--icon number-group__increment" type="button" aria-controls="quantity" data-step="1">
<span class="sr-only">Increase quantity</span>
<svg class="btn__icon" aria-hidden="true" fill="none" focusable="false" stroke-linecap="round" stroke-linejoin="round" stroke-width="3.5" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M12 5v14M5 12h14"/></svg>
</button>
</div>
<p class="form-description" id="quantity-help">Maximum 10 per order.</p>
</div>
document.querySelectorAll('[data-number-group]').forEach((group) => {
const input = group.querySelector('input');
const buttons = group.querySelectorAll('[data-step]');
const min = Number(input.min || -Infinity);
const max = Number(input.max || Infinity);
const update = () => {
const value = Math.min(max, Math.max(min, Number(input.value) || min));
input.value = value;
buttons.forEach((button) => {
button.disabled = Number(button.dataset.step) < 0 ? value <= min : value >= max;
});
};
buttons.forEach((button) => {
button.addEventListener('click', () => {
Number(button.dataset.step) < 0 ? input.stepDown() : input.stepUp();
update();
input.dispatchEvent(new Event('change', { bubbles: true }));
});
});
input.addEventListener('change', update);
update();
});