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: