Skip to content

Input stepper <l-input-stepper>

A stepper control that enhances a native <input type="number"> with decrement/increment buttons and an animated number track.

HTML tag<l-input-stepper>
Native
Progressive
Plain
Shadow-DOM
Progressive Custom Element

Options

Appearance

Pick a visual style via appearance. Each appearance has its own CSS import.

Default

Bordered box with inline buttons.

css
@import 'luxen-ui/css/input-stepper/default';
html
<l-input-stepper>
  <input
    type="number"
    value="1"
  />
</l-input-stepper>

Rounded

Circular standalone buttons with no container border — Airbnb-style.

css
@import 'luxen-ui/css/input-stepper/rounded';
html
<l-input-stepper appearance="rounded">
  <input
    type="number"
    min="1"
    max="10"
    value="1"
  />
</l-input-stepper>

Pill

Circular buttons inset into a filled capsule track — iOS-style quantity picker.

css
@import 'luxen-ui/css/input-stepper/pill';
html
<l-input-stepper appearance="pill">
  <input
    type="number"
    min="1"
    max="10"
    value="5"
  />
</l-input-stepper>

Size

Set the size attribute: xs, sm, md (default), lg, xl. All three appearances scale from the same control-height tokens, so their buttons match a button or input of the same size. pill then adds its inset around that row, which makes the capsule itself 8px taller than the other two at every size — give it its own line rather than a shared one when you put it in a row of form controls.

Code
html
<div class="flex flex-wrap gap-6 items-start">
  <div class="flex flex-col gap-4 items-start">
    <l-input-stepper size="xs">
      <input
        type="number"
        value="1"
      />
    </l-input-stepper>

    <l-input-stepper size="sm">
      <input
        type="number"
        value="1"
      />
    </l-input-stepper>

    <l-input-stepper size="md">
      <input
        type="number"
        value="1"
      />
    </l-input-stepper>

    <l-input-stepper size="lg">
      <input
        type="number"
        value="1"
      />
    </l-input-stepper>

    <l-input-stepper size="xl">
      <input
        type="number"
        value="1"
      />
    </l-input-stepper>
  </div>

  <div class="flex flex-col gap-4 items-start">
    <l-input-stepper
      appearance="rounded"
      size="xs"
    >
      <input
        type="number"
        value="1"
      />
    </l-input-stepper>

    <l-input-stepper
      appearance="rounded"
      size="sm"
    >
      <input
        type="number"
        value="1"
      />
    </l-input-stepper>

    <l-input-stepper
      appearance="rounded"
      size="md"
    >
      <input
        type="number"
        value="1"
      />
    </l-input-stepper>

    <l-input-stepper
      appearance="rounded"
      size="lg"
    >
      <input
        type="number"
        value="1"
      />
    </l-input-stepper>

    <l-input-stepper
      appearance="rounded"
      size="xl"
    >
      <input
        type="number"
        value="1"
      />
    </l-input-stepper>
  </div>

  <div class="flex flex-col gap-4 items-start">
    <l-input-stepper
      appearance="pill"
      size="xs"
    >
      <input
        type="number"
        value="1"
      />
    </l-input-stepper>

    <l-input-stepper
      appearance="pill"
      size="sm"
    >
      <input
        type="number"
        value="1"
      />
    </l-input-stepper>

    <l-input-stepper
      appearance="pill"
      size="md"
    >
      <input
        type="number"
        value="1"
      />
    </l-input-stepper>

    <l-input-stepper
      appearance="pill"
      size="lg"
    >
      <input
        type="number"
        value="1"
      />
    </l-input-stepper>

    <l-input-stepper
      appearance="pill"
      size="xl"
    >
      <input
        type="number"
        value="1"
      />
    </l-input-stepper>
  </div>
</div>

Not defined

Before JS loads (:not(:defined)), CSS provides a styled fallback with zero layout shift.

Code
html
<l-input-stepper>
  <input
    type="number"
    value="1"
  />
</l-input-stepper>

The CSS reserves space for the stepper buttons via padding-inline and matches the exact dimensions of the hydrated component. Once defined, the custom element replaces the padding with its own buttons.

Min / Max

Constrain the value range via min and max on the <input>.

Code
html
<l-input-stepper>
  <input
    type="number"
    min="0"
    max="10"
    value="5"
  />
</l-input-stepper>

Disabled

Native disabled attribute on the <input>.

Code
html
<div class="flex flex-wrap items-center gap-4">
  <l-input-stepper>
    <input
      type="number"
      value="3"
      disabled
    />
  </l-input-stepper>

  <l-input-stepper appearance="rounded">
    <input
      type="number"
      value="3"
      disabled
    />
  </l-input-stepper>

  <l-input-stepper appearance="pill">
    <input
      type="number"
      value="3"
      disabled
    />
  </l-input-stepper>
</div>

Roller

Enable the animated number roller overlay with with-roller.

Code
html
<div class="flex flex-wrap items-center gap-4">
  <l-input-stepper with-roller>
    <input
      type="number"
      min="0"
      max="99"
      value="5"
    />
  </l-input-stepper>

  <l-input-stepper
    appearance="rounded"
    with-roller
  >
    <input
      type="number"
      min="0"
      max="99"
      value="5"
    />
  </l-input-stepper>

  <l-input-stepper
    appearance="pill"
    with-roller
  >
    <input
      type="number"
      min="0"
      max="99"
      value="5"
    />
  </l-input-stepper>
</div>

Accessibility

Criteria

Role

Uses native <input type="number"> and native <button> elements — built-in semantics

WCAG4.1.2
RGAA11.1
Accessible name

The input must have an associated label via <label> or aria-label

WCAG1.3.1
RGAA11.1
Disabled state

Buttons disabled at min/max bounds; entire stepper disabled via disabled on the input

WCAG4.1.2
Form integration

Native <input> participates in form submission and validation directly

WCAG4.1.2

Rules

  • Wrap the stepper with a visible <label> or provide aria-label on the input element

Keyboard interactions

ArrowUp
Increments the value (native number input behavior)
ArrowDown
Decrements the value (native number input behavior)
Tab
Moves focus between the decrement button, input, and increment button

API reference

Importing

js
import 'luxen-ui/input-stepper';
css
@import 'luxen-ui/css/input-stepper/default';
/* or */
@import 'luxen-ui/css/input-stepper/rounded';
/* or */
@import 'luxen-ui/css/input-stepper/pill';

Attributes & Properties

minnumber | undefinedProperty
Minimum allowed value. Falls back to the input's min attribute.
maxnumber | undefinedProperty
Maximum allowed value. Falls back to the input's max attribute.
stepnumber | undefinedProperty
Step increment. Falls back to the input's step attribute.
sizeInputStepperSizedefault:'md'Property
Control size.
with-rollerbooleandefault:falseProperty
Enable the animated number roller overlay.
decrement-iconstringdefault:'lucide:minus'Property
Icon name for the decrement button.
increment-iconstringdefault:'lucide:plus'Property
Icon name for the increment button.

Events

changeEvent
Fired when the value changes. Bubbles. Properties: value: number.

CSS custom properties

--border-colordefault:--l-color-borderCustom property
Edge color of the stepper container (default and pill appearances) and of each button (rounded appearance).
--border-radiusdefault:--radius-mdCustom property
Border radius of the stepper container (default appearance).