Forms

Duration Picker

A drag-and-keyboard friendly duration picker with animated digit transitions.

A time duration input that lets you adjust hours, minutes, and seconds by dragging vertically (or touching), using arrow keys, typing digits, or jumping with Page/Home/End. Digits animate with spring transitions as values change.

Usage

Bind the total duration in seconds with v-model. You can customize which units are shown, the range, step, and label.

Duration
00
01
30
<template>
  <div class="flex w-full max-w-md justify-center">
    <MDurationPicker
      v-model="duration"
      label="Duration"
    />
  </div>
</template>

<script setup lang="ts">
const duration = ref(90)
</script>

Customize

Duration
00
01
30

Customize

Seconds90
Units3
hours
minutes
seconds
Min0
Max86399
Step1
LabelDuration
Disabled

Props

Prop Type Default Description
modelValuenumber0The total duration in seconds. Use with v-model to control the value.
unitsDurationUnit[]['hours', 'minutes', 'seconds']Which units to display. Supported units are 'hours', 'minutes', and 'seconds'.
minnumber0Minimum allowed duration in seconds.
maxnumber359999Maximum allowed duration in seconds (default is 99:59:59).
stepnumber1Step size in seconds used for arrow key and drag increments.
labelstring''Optional label displayed above the picker.
disabledbooleanfalseWhether the picker is disabled.

Events

Event Payload Description
update:modelValue(value: number) => voidEmitted when the duration value changes.

Slots

Slot Props Description
default-The default slot is not used by this component.

UI

This component's theme exposes the slots below. Override any of them per instance with the ui prop, or app-wide under ui.durationPicker in app.config.ts.

Prop Type Default Description
uiobject-Per-slot classes merged over the theme defaults.
Slot Props Description
root-inline-flex flex-col gap-1.5
label-text-xs font-medium text-muted
group-inline-flex items-center gap-0.5 rounded-lg border border-default bg-default/40 px-2 py-1.5
separator-mx-0.5 text-muted select-none
segment-relative flex select-none items-center justify-center rounded-md px-1 py-0.5 font-medium text-highlighted tabular-nums outline-none focus-visible:ring-2 focus-visible:ring-primary cursor-ns-resize
digit-relative overflow-hidden text-center
digitChar-absolute inset-0 flex items-center justify-center

Accessibility

The picker uses role="group" with an aria-label="Duration", and each unit segment is a role="spinbutton" with aria-label (hours/minutes/seconds), aria-valuemin, aria-valuemax, and aria-valuenow. Keyboard navigation is supported: ArrowUp/ArrowDown adjust values, ArrowLeft/ArrowRight move between segments, PageUp/PageDown adjust by larger steps, Home/End jump to min/max of current segment, and digits allow typing two-digit values with a timeout to commit. Focus management keeps the active segment focused. Drag/touch interactions use pointer events with touch-action: none.

Performance

  • Only the changing digit characters animate via AnimatePresence and spring physics, keeping layout stable.
  • Drag scrubbing accumulates deltas and applies changes in step increments to avoid excessive updates.
  • Touch/mouse listeners are added on interaction and cleaned up on unmount/drag end.
  • Transform-based animations avoid layout reflow for digit transitions.
  • Reduced motion considerations are handled by the animation library's spring behavior (not explicit prefers-reduced-motion check in source, but animations remain smooth).
Drag vertically on any digit segment to scrub the value quickly. Hold Shift while using Arrow keys to adjust by 10x the step size.
Copyright © 2026