# useCooldown

> A React cooldown timer hook for resend and retry buttons: wait N seconds before a code can be sent again, kept across reloads.

- Type: React hook (Hooks & Utilities)
- Access: Pro (the source needs a Wingo UI Pro plan, see https://wingo-ui.com/pricing)
- Version: 1.0.0
- Install: `npx wingo-ui@latest add use-cooldown`
- Docs: https://wingo-ui.com/components/use-cooldown

## Interaction

Press Resend code: the button locks and counts down (0:30, 0:29...) while the ring fills, then unlocks. Reload the page with a persist key and the wait continues where it was. Reset stops it at once. The label updates once a second, on the second.

## Installation

```bash
npx wingo-ui@latest add use-cooldown
```

Pro item: run `npx wingo-ui@latest login` once with a Pro account. The CLI adds the files, their dependencies and a version record for updates.

With shadcn: `npx shadcn@latest add @wingo-ui/use-cooldown` (needs the @wingo-ui registry with your API key in components.json)

Files:

- `hooks/use-cooldown.ts`

Wingo UI dependencies (installed automatically):

- [usePersistedState](https://wingo-ui.com/components/use-persisted-state) ([Markdown](https://wingo-ui.com/components/use-persisted-state.md))

## Usage

```tsx
import { Button } from "@/components/ui/button"
import { useCooldown } from "@/hooks/use-cooldown"

export function ResendCode({ onResend }: { onResend: () => Promise<void> }) {
  const resend = useCooldown({ seconds: 30, autoStart: true, persistKey: "otp-resend" })

  return (
    <Button
      variant="link"
      disabled={resend.active}
      onClick={async () => {
        await onResend()
        resend.start()
      }}
    >
      {resend.active ? `Resend code in ${resend.label}` : "Resend code"}
    </Button>
  )
}
```

## API

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `seconds?` | `number` | `30` | Length of one wait in seconds. start(seconds) overrides it for a single wait (a longer lock after 3 failed attempts). |
| `autoStart?` | `boolean` | `false` | Start waiting on mount, for screens that open right after a code was sent (check your email, enter the SMS code). The server and hydration render the full wait, so the label never jumps. |
| `persistKey?` | `string` | `""` | Keeps the end time in local storage under this key, so a reload does not unlock the button early. Empty keeps it in memory. |
| `onEnd?` | `() => void` |  | Runs once when a wait reaches zero (not on reset). |
| `useCooldown?` | `(options?: UseCooldownOptions) => Cooldown` |  | Returns { remaining (whole seconds), active, endsAt, label ("0:42"), progress (0 to 1 for a ring), start(seconds?), reset() }. It ticks with one timeout per second aligned to the end time: one re-render a second, exact after the tab sleeps. |

## Parts

### formatCooldown (function)

Minutes and zero-padded seconds: 42 is "0:42", 65 is "1:05". Signature: (seconds: number) => string.

### UseCooldownOptions (type)

The options object: { seconds?: number; autoStart?: boolean; persistKey?: string; onEnd?: () => void }.

### Cooldown (type)

The return value: remaining, active, endsAt, label, progress, start(seconds?) and reset().

## Examples

- **Starts on mount**: The screen opens right after the code was sent.
- **Survives a reload**: persistKey keeps the end time in local storage; reload mid-wait.
- **Inline link**: A 15 second wait on a link under a form.

## See also

- [OTP Input](https://wingo-ui.com/components/otp-input) ([Markdown](https://wingo-ui.com/components/otp-input.md))
- [Countdown](https://wingo-ui.com/components/countdown) ([Markdown](https://wingo-ui.com/components/countdown.md))
- [usePersistedState](https://wingo-ui.com/components/use-persisted-state) ([Markdown](https://wingo-ui.com/components/use-persisted-state.md))
- [Button](https://wingo-ui.com/components/button) ([Markdown](https://wingo-ui.com/components/button.md))

---

Canonical: https://wingo-ui.com/components/use-cooldown
More components: https://wingo-ui.com/components · Index for agents: https://wingo-ui.com/llms.txt
