Duration Picker
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.
<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
Customize
Props
| Prop | Type | Default | Description |
|---|---|---|---|
modelValue | number | 0 | The total duration in seconds. Use with v-model to control the value. |
units | DurationUnit[] | ['hours', 'minutes', 'seconds'] | Which units to display. Supported units are 'hours', 'minutes', and 'seconds'. |
min | number | 0 | Minimum allowed duration in seconds. |
max | number | 359999 | Maximum allowed duration in seconds (default is 99:59:59). |
step | number | 1 | Step size in seconds used for arrow key and drag increments. |
label | string | '' | Optional label displayed above the picker. |
disabled | boolean | false | Whether the picker is disabled. |
Events
| Event | Payload | Description |
|---|---|---|
update:modelValue | (value: number) => void | Emitted 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 |
|---|---|---|---|
ui | object | - | 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
AnimatePresenceand 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-motioncheck in source, but animations remain smooth).