useDelay
Schedule open and close transitions with start, stop, pause, and resume controls and a reactive view of the pending delay.
Usage
The useDelay composable is the scheduling primitive behind hover-driven UI like tooltips, popovers, and menus. It mirrors the useTimer lifecycle — start, stop, pause, resume, plus reactive isActive, isPaused, remaining — and adds direction tracking via isOpening and a promise that resolves once the delay elapses.
import { useDelay } from '@vuetify/v0'
const delay = useDelay(isOpening => {
isVisible.value = isOpening
}, {
openDelay: 300,
closeDelay: 200,
})
// Schedule an open
await delay.start(true)
// Schedule a close, with a minimum delay of 500ms
await delay.start(false, { minDelay: 500 })
// Pause and resume the in-flight delay
delay.pause()
delay.resume()
// Cancel any pending transition
delay.stop()Reactive delays Both openDelay and closeDelay accept refs, getters, or plain values. Pass a getter to vary the delay based on input mode — for example, 0 for keyboard focus and 300 for hover.
Architecture
start() cancels any in-flight delay before scheduling the next one — and the previous promise resolves with the new direction so awaiting code observes the latest intent. stop() cancels without scheduling a replacement; the pending promise resolves with the current isOpening direction. The pending timer is cleared on scope disposal.
Reactivity
| Property | Type | Description |
|---|---|---|
start | (isOpening: boolean, options?: UseDelayStartOptions) => Promise<boolean> | Start a delay; resolves with isOpening |
stop | () => void | Cancel any pending delay and reset state |
pause | () => void | Pause the in-flight delay, preserving remaining time |
resume | () => void | Resume from where pause left off |
isActive | Readonly<Ref<boolean>> | true while a delay is pending |
isPaused | Readonly<Ref<boolean>> | true after pause(), false after resume() or stop() |
remaining | Readonly<Ref<number>> | Milliseconds left until the pending transition fires |
isOpening | Readonly<Ref<boolean>> | Direction of the pending transition (true = opening) |
Examples
Hover with Pause/Resume
Hover the target to schedule a 2000 ms open; leave it to schedule a 1500 ms close. The progress bar reflects remaining against the active direction, the badges surface every reactive flag, and the controls demonstrate pause, resume, and stop against the in-flight delay.
Reach for this pattern when you want a tooltip or popover that respects hover intent without flickering. Pause/Resume is the differentiator — without it, briefly leaving the target to interact with adjacent UI would restart the close countdown. The promise returned by start() lets you sequence side effects after the delay elapses without a second watch.
| File | Role |
|---|---|
basic.vue | Demonstrates hover-driven open/close with pause/resume |
Key Features
Lifecycle Vocabulary
useDelay exposes the same lifecycle surface as useTimer (start, stop, pause, resume, isActive, isPaused, remaining) and adds an isOpening direction flag. Consumers that already know useTimer get the same shape with one extra dimension — direction.
Reactive Delays
openDelay and closeDelay accept any MaybeRefOrGetter<number>. Resolution happens when start() is called, so updates after start do not affect the in-flight delay.
import { shallowRef } from 'vue'
const fast = shallowRef(false)
const delay = useDelay({
openDelay: () => fast.value ? 0 : 500,
closeDelay: 200,
})Minimum Delay Floor
Pass minDelay to start() to enforce a floor on the resolved delay. Useful for transient feedback like toasts where the close delay must not be shorter than the time the content has been visible.
const delay = useDelay({ closeDelay: 200 })
await delay.start(true)
// User dismisses immediately — still hold for 500ms total
await delay.start(false, { minDelay: 500 })Re-Start Behavior
Calling start() while another delay is pending cancels the previous timer and resolves the previous promise with the new direction. Awaiting code sees a single source of truth — the latest intent — instead of stale resolutions.
delay.start(true)
// User leaves before openDelay elapses
delay.start(false) // previous promise resolves false, new close delay beginsPause and Resume
Pause preserves the remaining delay; resume continues from where pause left off. Useful for hover-driven UI where the user briefly interacts with the panel itself and you don’t want the auto-close timer to keep counting.
const delay = useDelay({ closeDelay: 1000 })
delay.start(false)
// 300ms later — user moves into the panel
delay.pause() // remaining ≈ 700ms
// User moves out
delay.resume() // fires after ~700 more msAutomatic Cleanup
The pending timer clears on scope disposal — no manual cleanup needed.
// Timer automatically clears when component unmounts
const delay = useDelay({ openDelay: 300 })FAQ
The promise resolves once the delay elapses, letting consumers await the transition or chain follow-up work. If start() is called again before the delay completes, the previous promise resolves with the new direction.
A floor on the resolved delay. start(false, { minDelay: 500 }) schedules the close after max(closeDelay, 500) ms. Useful when transient UI must remain visible for a minimum time before dismissal.
No — the underlying timer is cleared on scope disposal, and any pending promise resolves with the current isOpening value. Consumers that need to distinguish a natural delay completion from a scope teardown should track that distinction externally (e.g., a boolean flag set inside the callback). Reset the in-flight delay manually if you need it to survive a remount.
Reach for useDelay when you need separate open and close durations with direction tracking. Reach for useTimer directly when you need a single duration with no open/close split — for example, debouncing or one-shot fire-after-N-ms.
Functions
useDelay
(callback?: ((isOpening: boolean) => void) | undefined, options?: UseDelayOptions) => UseDelayReturnSchedule open and close transitions with configurable delays. Composes `useTimer` for the lifecycle (`start` / `stop` / `pause` / `resume` plus reactive `isActive`, `isPaused`, `remaining`) and adds direction tracking (`isOpening`) and promise-based resolution.
Options
openDelay
MaybeRefOrGetter<number> | undefinedDelay in milliseconds before the opening transition fires.
closeDelay
MaybeRefOrGetter<number> | undefinedDelay in milliseconds before the closing transition fires.
Properties
isOpening
Readonly<Ref<boolean, boolean>>Direction of the pending transition (`true` = opening, `false` = closing).
Methods
start
(isOpening: boolean, options?: UseDelayStartOptions) => Promise<boolean>Start a delay in the given direction. Restarts if already running. Resolves with `isOpening` once the delay elapses. If `start()` is called again before the previous resolves, the previous promise resolves with the new direction.
stop
() => voidCancel any pending delay and reset state. Resolves the pending promise with the current direction.