Skip to content

Numeric fields with increment / decrement buttons

Create numeric input fields with custom buttons using QInput and QBtn or change value by dragging.

quasar
by
PDan

Problem description ​

You need to create numeric form field with buttons to increase / decrease the value.

Solution ​

Use a base QInput and use the #before, #prepend #append, or #after slots to place the buttons.

Use vTouchRepeat to change value as long as a button is pressed.

Use vTouchPan to change the value by dragging.

vue
<template>
  <div class="col column no-wrap">
    <div class="q-pa-md column q-gutter-y-md" style="max-width: 400px">
      <q-input
        style="max-width: 200px"
        v-model.number="model"
        type="number"
        :step="1"
        standout
        dense
        color="primary"
        input-class="text-right q-no-input-spinner"
      >
        <template v-slot:prepend>
          <q-btn
            style="margin-left: -12px; border-top-right-radius: 0; border-bottom-right-radius: 0"
            unelevated
            color="red-6"
            icon="remove"
            padding="sm"
            v-touch-repeat:0:500:100.mouse.enter.space="decrement"
          />
        </template>

        <template v-slot:append>
          <q-btn
            style="margin-right: -12px; border-top-left-radius: 0; border-bottom-left-radius: 0"
            unelevated
            color="green-6"
            icon="add"
            padding="sm"
            v-touch-repeat:0:500:100.mouse.enter.space="increment"
          />
        </template>
      </q-input>

      <q-input
        style="max-width: 200px"
        v-model.number="model"
        type="number"
        :step="1"
        outlined
        dense
        color="primary"
        input-class="text-right q-no-input-spinner"
      >
        <template v-slot:prepend>
          <q-icon
            style="cursor: ns-resize"
            name="swap_vert"
            v-touch-pan.vertical.mouse.prevent="onPan"
          />
        </template>

        <template v-slot:append>
          <q-btn
            flat
            color="primary"
            icon="remove_circle_outline"
            padding="7px"
            v-touch-repeat:0:500:100.mouse.enter.space="decrement"
          />

          <q-btn
            style="margin-right: -11px"
            flat
            color="primary"
            icon="add_circle_outline"
            padding="7px"
            v-touch-repeat:0:500:100.mouse.enter.space="increment"
          />
        </template>
      </q-input>
    </div>
  </div>
</template>

<script setup lang="ts">
import { ref } from 'vue';

const model = ref(0);
let onPanTimer = null;
let onPanValue = 0;

function increment() {
  model.value += 1;
}

function decrement() {
  model.value -= 1;
}

function onPan({ offset, isFirst, isFinal }) {
  const sign = Math.sign(-offset.y);
  const dist = Math.max(0, Math.abs(offset.y) - 5);
  const newPanValue = sign * Math.ceil(dist / 20);

  if (onPanTimer !== null) {
    clearInterval(onPanTimer);
  }

  if (isFinal !== true && newPanValue !== 0) {
    if (newPanValue !== onPanValue) {
      onPanValue = newPanValue;
      model.value += onPanValue;
    }
    onPanTimer = setInterval(() => {
      model.value += onPanValue;
    }, Math.max(20, 1000 / dist));
  } else {
    onPanTimer = null;
  }
}
</script>

Demo ​

Last updated: