Vize

use-countdown

Count down from a number of ticks to zero.

Package @vizejs/composable/use-countdown
Own the source vize lib pull composable:use-countdown
Runtime exports useCountdown
Gzip budget 2560 B

Minimal setup

Use a Vue 3.5+ project with Vue SFC compilation configured (see Vite+ integration).

vp install @vizejs/composable

Import the entry directly. Call lifecycle-bound utilities inside <script setup> so cleanup follows the component scope; consult the runtime contract for this entry's SSR and hydration behavior.

import { useCountdown } from "@vizejs/composable/use-countdown";

Try it

Wait before resending a sign-in code

Count down a cooldown after an action, keep the button disabled until it ends, and allow another request afterwards.

Open the interactive example

What to observe: Send code starts a 30-second cooldown and disables the button while the remaining seconds count down. When it reaches zero the button reads Resend code again and the message counts the codes sent.

start() resets to the initial count and begins ticking; isActive is true while the timer runs. Call useCountdown inside setup so its interval is cleared with the component. Pass intervalMs for a different tick length.

Compare the initial and interacted states
Wait before resending a sign-in code: initial state
Initial state
Wait before resending a sign-in code: state after the verified interactions
After the verified interactions

Copy the example

Save this complete SFC in your Vue project. Its public imports and source are the same ones executed in the preview.

<script setup lang="ts">
import { ref } from "vue";
import { useCountdown } from "@vizejs/composable/use-countdown";

const sent = ref(0);
const { remaining, isActive, start } = useCountdown(30);

function sendCode() {
  sent.value += 1;
  start();
}
</script>

<template>
  <div class="composable-example">
    <p>Let a reader request a new sign-in code, then wait before asking again.</p>
    <button type="button" :disabled="isActive" @click="sendCode">
      {{ sent === 0 ? "Send code" : "Resend code" }}
    </button>
    <output aria-live="polite">{{
      sent === 0
        ? "No code sent yet."
        : isActive
          ? `Code sent. Resend available in ${remaining} s.`
          : `Code sent ${sent} times. You can request another one.`
    }}</output>
  </div>
</template>

Runtime contract

Utility Category Stability SSR Hydration Cleanup Targets Host globals Uses
useCountdown timing experimental deterministic-fallback stable caller, reactive-scope web, server, worker, native, desktop, terminal globalThis, window useIntervalFn

API

useCountdown

Count down from a number of ticks to zero. Builds on useIntervalFn: pause/resume keep the remaining count, start and reset restore it, and the timer pauses itself at zero after calling onComplete. The initial count is reactive only for later start()/reset() calls without an argument. Server rendering is hydration-stable (no timer, remaining stays at the initial count).

function useCountdown( initialCount: MaybeRefOrGetter<number>, options: UseCountdownOptions = {}, ): CountdownControls
const { remaining, start } = useCountdown(60, { onComplete: resendAvailable });
start();

Types

UseCountdownOptions

Options for useCountdown.

Member Type Description
intervalMs? MaybeRefOrGetter<number> Milliseconds between two decrements. Reactive.
immediate? boolean Start counting down as soon as the composable is created.
onTick? (remaining: number) => void Invoked after every decrement with the remaining count.
onComplete? () => void Invoked once when the count reaches zero.
runOnServer? boolean Starts host timers when no browser window is available.
scheduler? IntervalScheduler Repeating timer host.

CountdownControls

State and controls returned by useCountdown.

Member Type Description
isActive Readonly<ShallowRef<boolean>> Whether the timer is logically running. On the server (without runOnServer) this reflects the requested state without starting a host timer, so server and client render the same initial value.
pause () => void Stop ticking. Idempotent.
resume () => void Start (or keep) ticking. Idempotent.
remaining Readonly<ShallowRef<number>> Remaining whole ticks; never negative.
isComplete ComputedRef<boolean> Whether the countdown reached zero.
start (count?: number) => void Reset to count (default: the initial count) and start ticking.
reset (count?: number) => void Pause and reset to count (default: the initial count).
stop () => void Pause and jump to zero without firing onComplete.