The useTimer composable creates a controllable timer that fires a handler after a specified duration. It supports pause/resume, repeating intervals, and reactive remaining time tracking.
ts
Open in Vuetify Bin
Code size: medium
Enable line wrap
Copy code
import { useTimer } from '@vuetify/v0'const timer =useTimer(() => { console.log('Timer fired!')}, { duration: 5000 })// Control the timertimer.start()timer.pause()timer.resume()timer.stop()// Reactive statetimer.remaining.value // ms left until next firetimer.isActive.value // true when started (even if paused)timer.isPaused.value // true when paused
Tip
Replaces debounce debounce was removed. useTimer is a pause/resume timer. start() restarts the full duration. It does not collect arguments from repeated calls the way debounce did.
The timer tracks three internal values: startedAt (timestamp when the current run began), budget (remaining ms for the current run), and duration (the original interval). On pause, budget is reduced by elapsed time. On resume, a new setTimeout is created with the remaining budget.
A 10-second countdown timer that exercises all four controls — start, stop, pause, resume — and displays the reactive remaining value as both a seconds readout and a progress bar. toRef(() => Math.ceil(remaining.value / 1000)) converts the millisecond ref to a human-readable ceiling count; toRef(() => (remaining.value / duration) * 100) drives the bar width. Three state badges at the bottom reflect isActive, isPaused, and the raw remaining ms so you can watch all three change as you operate the controls.
Pausing captures the remaining budget so resume continues from exactly where it left off. Stopping resets the timer so start begins a fresh 10-second run. Reach for this pattern for user-facing delays (auto-dismiss dialogs, OTP expiry, resend-code cooldowns) where the user may need to pause and resume. For auto-dismissing queued notifications, see the Toast Notifications example below; for interval-based work (polling, animation ticks), use repeat: true.
Auto-dismissing toast notifications where each Toast.vue instance owns its own useTimer. The timer fires the onDismiss callback when the duration elapses, removing the toast from the list. Hovering pauses the countdown via @pointerenter="pause" and resumes it via @pointerleave="resume", giving users time to read without losing the notification. A shrinking progress bar driven by remaining makes the remaining time visible without any polling or watchers.
The key design point is one timer per toast, not a single shared timer. Each toast tracks its own independent countdown so toasts added at different times dismiss independently. useToast.ts stores the toast array and provides add and dismiss helpers; toasts.vue wires the trigger buttons to add() and passes onDismiss as a prop to each Toast.vue. This composition pattern generalizes to any queue of items that each need their own timed lifecycle — drag-to-dismiss sheets, loading overlays with a timeout, or step-through tutorials. For the underlying queue primitive, see createQueue.
File
Role
useToast.ts
Toast state — reactive array with add/dismiss helpers
useTimer is a setTimeout-based delay or interval with pause/resume and remaining-time tracking — reach for it for user-facing delays like auto-dismiss or cooldowns. useRaf throttles a callback to animation frames, for per-frame work like scroll or animation updates.
No. pause() captures the remaining budget and resume() continues from exactly where it left off, not from the full duration.
It restarts from the full duration. A timer started for 5s that you start() again after 2s will fire 5s later, not 3s.
Pass repeat: true. By default the timer is one-shot — it fires once and isActive becomes false; with repeat it restarts after each fire until you call stop().
Nothing with the same call shape. useTimer is a pause/resume timer. start() restarts the full duration and the handler is the one you passed in. It does not collect arguments from repeated calls.
Discord
Need help? Join our community for support and discussions ↗