Forms inputstepper
Numeric Stepper
Input with +/- increment buttons.
Preview
Usage
Copy the full block below to use this component with its imports.
astro
---
import { NumericStepper } from 'astro-component-kit';
---
<StepperInput id="qty" label="Quantity" value={1} min={1} max={10} /> ---
import { NumericStepper } from 'astro-component-kit';
---
<StepperInput id="qty" label="Quantity" value={1} min={1} max={10} /> Manual Installation
If you are not using the npm package, create a new file src/components/lib/NumericStepper.astro and paste the following code:
astro
---
/**
* StepperInput — A numeric input with increment and decrement buttons for precise control.
*
* @param {string} label - Optional input label.
* @param {string} id - HTML ID for the number input.
* @param {string} name - HTML name binding.
* @param {number} value - Initial default value. Default is 1.
* @param {number} min - Minimum allowed value.
* @param {number} max - Maximum allowed value.
* @param {number} step - Numeric increment step. Default is 1.
*/
interface Props {
label?: string;
id: string;
name?: string;
value?: number;
min?: number;
max?: number;
step?: number;
}
const { label, id, name, value = 1, min, max, step = 1 } = Astro.props;
---
<div class="stepper-container">
{label && <label class="stepper-label" for={id}>{label}</label>}
<div class="stepper" data-stepper>
<button type="button" class="stepper-btn stepper-btn--minus" data-step-minus aria-label="Decrease value">−</button>
<input
type="number"
{id}
{name}
{min}
{max}
{step}
value={value}
class="stepper-input"
data-step-input
/>
<button type="button" class="stepper-btn stepper-btn--plus" data-step-plus aria-label="Increase value">+</button>
</div>
</div>
<style>
.stepper-container { display: flex; flex-direction: column; gap: var(--sp-2, 0.5rem); }
.stepper-label { font-size: 0.85rem; font-weight: 600; color: var(--c-text-2, #94a3b8); margin-left: 0.5rem; }
.stepper {
display: flex; align-items: center;
background: var(--c-bg-elev, rgba(255,255,255,0.05));
border-radius: var(--r-md, 12px);
border: 1px solid var(--c-border, rgba(255,255,255,0.1));
overflow: hidden;
width: fit-content;
}
.stepper-btn {
width: 40px; height: 40px;
background: var(--c-primary, #6366f1);
border: none; color: #fff;
font-size: 1.25rem; font-weight: 600;
cursor: pointer; transition: all 0.2s;
display: grid; place-items: center;
}
.stepper-btn:hover { background: var(--c-primary-light, #818cf8); }
.stepper-btn:active { transform: scale(0.95); }
.stepper-input {
width: 60px; height: 40px;
background: transparent;
border: none; text-align: center;
color: var(--c-text-1, #fff);
font-size: 1rem; font-weight: 700;
outline: none; -moz-appearance: textfield;
font-family: inherit;
}
.stepper-input::-webkit-inner-spin-button,
.stepper-input::-webkit-outer-spin-button { display: none; }
</style>
<script>
document.querySelectorAll('[data-stepper]').forEach(wrapper => {
const input = wrapper.querySelector('[data-step-input]') as HTMLInputElement;
const minus = wrapper.querySelector('[data-step-minus]');
const plus = wrapper.querySelector('[data-step-plus]');
if (!input || !minus || !plus) return;
const step = parseFloat(input.step) || 1;
const min = input.min !== "" ? parseFloat(input.min) : -Infinity;
const max = input.max !== "" ? parseFloat(input.max) : Infinity;
minus.addEventListener('click', () => {
const current = parseFloat(input.value) || 0;
if (current - step >= min) input.value = (current - step).toString();
});
plus.addEventListener('click', () => {
const current = parseFloat(input.value) || 0;
if (current + step <= max) input.value = (current + step).toString();
});
});
</script> ---
/**
* StepperInput — A numeric input with increment and decrement buttons for precise control.
*
* @param {string} label - Optional input label.
* @param {string} id - HTML ID for the number input.
* @param {string} name - HTML name binding.
* @param {number} value - Initial default value. Default is 1.
* @param {number} min - Minimum allowed value.
* @param {number} max - Maximum allowed value.
* @param {number} step - Numeric increment step. Default is 1.
*/
interface Props {
label?: string;
id: string;
name?: string;
value?: number;
min?: number;
max?: number;
step?: number;
}
const { label, id, name, value = 1, min, max, step = 1 } = Astro.props;
---
<div class="stepper-container">
{label && <label class="stepper-label" for={id}>{label}</label>}
<div class="stepper" data-stepper>
<button type="button" class="stepper-btn stepper-btn--minus" data-step-minus aria-label="Decrease value">−</button>
<input
type="number"
{id}
{name}
{min}
{max}
{step}
value={value}
class="stepper-input"
data-step-input
/>
<button type="button" class="stepper-btn stepper-btn--plus" data-step-plus aria-label="Increase value">+</button>
</div>
</div>
<style>
.stepper-container { display: flex; flex-direction: column; gap: var(--sp-2, 0.5rem); }
.stepper-label { font-size: 0.85rem; font-weight: 600; color: var(--c-text-2, #94a3b8); margin-left: 0.5rem; }
.stepper {
display: flex; align-items: center;
background: var(--c-bg-elev, rgba(255,255,255,0.05));
border-radius: var(--r-md, 12px);
border: 1px solid var(--c-border, rgba(255,255,255,0.1));
overflow: hidden;
width: fit-content;
}
.stepper-btn {
width: 40px; height: 40px;
background: var(--c-primary, #6366f1);
border: none; color: #fff;
font-size: 1.25rem; font-weight: 600;
cursor: pointer; transition: all 0.2s;
display: grid; place-items: center;
}
.stepper-btn:hover { background: var(--c-primary-light, #818cf8); }
.stepper-btn:active { transform: scale(0.95); }
.stepper-input {
width: 60px; height: 40px;
background: transparent;
border: none; text-align: center;
color: var(--c-text-1, #fff);
font-size: 1rem; font-weight: 700;
outline: none; -moz-appearance: textfield;
font-family: inherit;
}
.stepper-input::-webkit-inner-spin-button,
.stepper-input::-webkit-outer-spin-button { display: none; }
</style>
<script>
document.querySelectorAll('[data-stepper]').forEach(wrapper => {
const input = wrapper.querySelector('[data-step-input]') as HTMLInputElement;
const minus = wrapper.querySelector('[data-step-minus]');
const plus = wrapper.querySelector('[data-step-plus]');
if (!input || !minus || !plus) return;
const step = parseFloat(input.step) || 1;
const min = input.min !== "" ? parseFloat(input.min) : -Infinity;
const max = input.max !== "" ? parseFloat(input.max) : Infinity;
minus.addEventListener('click', () => {
const current = parseFloat(input.value) || 0;
if (current - step >= min) input.value = (current - step).toString();
});
plus.addEventListener('click', () => {
const current = parseFloat(input.value) || 0;
if (current + step <= max) input.value = (current + step).toString();
});
});
</script>
Quick Info
- Category
- Forms
- Filename
NumericStepper.astro- Dependencies
- None — pure Astro + CSS
- Tags
- inputstepper