> For the complete documentation index, see [llms.txt](https://analog.groupthought.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://analog.groupthought.com/reference/alpine/stepper.md).

# x-stepper

Directives and a magic for a number input with plus and minus buttons.

Directives and a magic for a number input with plus and minus buttons.

| Directive / magic | What it does                                                                                                      |
| ----------------- | ----------------------------------------------------------------------------------------------------------------- |
| `x-stepper`       | The root. Holds the stepper state. A click on its own background focuses the input                                |
| `x-stepper:input` | The input. Must be an `<input type="number">`; its `min`, `max`, and `step` attributes set the limits             |
| `x-stepper:minus` | The minus button. Must be a `<button>`. Calls `stepDown()` on the input                                           |
| `x-stepper:plus`  | The plus button. Must be a `<button>`. Calls `stepUp()` on the input                                              |
| `$stepper`        | The state, readable anywhere inside the root: `value` (the input value as a number), `increment()`, `decrement()` |

The buttons use the input's native `stepDown()` and `stepUp()`, so the browser clamps the value to `min` and `max` and moves by `step`. Browsers do not dispatch `input` or `change` events for those calls, so the plugin dispatches them manually and `x-model` on the input keeps working. Each step also dispatches an `increment` or `decrement` event whose detail is the new value.

The plugin doesn't announce changes. Give each button an `aria-label` and use an `<output>` for the value, as shown in the last example.

## Examples

### A basic stepper

Use `x-stepper:minus`, `x-stepper:input`, and `x-stepper:plus` inside an `x-stepper` root:

```html
<div x-stepper>
  <button type="button" x-stepper:minus>−</button>
  <input type="number" name="quantity" min="1" max="10" step="1" value="1" x-stepper:input>
  <button type="button" x-stepper:plus>+</button>
</div>
```

### Styling the buttons at the limits

Use `$stepper.value` against the input's `min` and `max` to fade a button that can no longer step:

```html
<button
  type="button"
  x-stepper:minus
  :class="{ 'pointer-events-none opacity-30': $stepper.value <= 1 }"
>
  −
</button>
```

### Submitting on change

Use the `increment` and `decrement` events on the root to save each step. The cart line item submits its quantity this way:

```html
<div
  x-stepper
  @increment.debounce.500ms="$cartItem.submitAsync()"
  @decrement.debounce.500ms="$cartItem.submitAsync()"
>
  …
</div>
```

The debounce collapses a run of clicks into one request. See [x-cart-item](/reference/alpine/cart-item.md) for the cart side.

### Announcing the value

Use an `<output>` with `x-text="$stepper.value"` so screen readers hear each step, and label the icon-only buttons:

```liquid
<div x-stepper>
  <button type="button" x-stepper:minus aria-controls="quantity" aria-label="{{ 'product.accessibility.stepper_minus' | t }}">−</button>
  <input type="number" id="quantity" name="quantity" min="1" value="1" x-stepper:input>
  <button type="button" x-stepper:plus aria-controls="quantity" aria-label="{{ 'product.accessibility.stepper_plus' | t }}">+</button>

  <output class="sr-only" for="quantity" aria-live="assertive" x-text="$stepper.value"></output>
</div>
```

## Customizing

The theme renders its steppers through the `stepper` snippet, which adds the sizes, icons, and the animated wheel. The cart quantity behavior built on it is described in [Cart](/features/cart.md).
