Resend with Success Toast
The full featured pattern for OTP, verification email, and password reset flows where the user needs explicit confirmation the action happened before the cooldown starts counting
<button type="button" class="nds-btn nds-primary nds-cooldown"
data-cooldown="30"
data-cooldown-loading="3"
data-cooldown-label="Resend in {s}s"
data-resend-label="Resend"
data-sent-title="Verification code sent"
data-sent-message="A new code has been sent to your mobile number.">
<span class="nds-label">Send code</span>
</button>
Simple Cooldown
For rate-limited retry buttons where you just need to prevent rapid repeats without a confirmation step
<button type="button" class="nds-btn nds-secondary nds-cooldown"
data-cooldown="10"
data-cooldown-label="Try again in {s}s">
<span class="nds-label">Try again</span>
</button>
Built-in Features
Activates on every .nds-cooldown on the page and on any element added later. No wiring code required.
Holds the button in the standard data-state="loading" style for the configured number of seconds before the countdown starts.
Swaps the button label to your template every second, with {s} replaced by the seconds remaining, until the cooldown ends.
Fires a success toast at the bottom of the viewport when the cooldown begins if data-sent-title or data-sent-message is set.
After the first completed cycle the button can show a different label (for example "Send code" becomes "Resend"). Abort during the loading phase keeps the original label.
Trigger the cycle from JS, abort a cooldown in flight, and hook four lifecycle events to wire your own side effects around the built-in behavior.
Usage Guidelines
Best Practices
- Use for resend flows where the backend imposes a per-user rate limit (OTP, verification email, password reset) and you want the UI to match that limit exactly
- Use for retry buttons after a failed request, to stop users from hammering an endpoint that is already struggling
- Use the optional loading phase to model a real network round trip: set
data-cooldown-loadingto an estimate of the request duration, or triggerNDS.CooldownButton.reset()from your response handler once the real request resolves - Do not use this component as a generic submit guard for forms. Use a regular disabled state tied to the form's submission lifecycle instead
- Do not use it for long cooldowns (over a few minutes). The countdown reads as nagging and ties the user to the page. Show a timestamp and refresh-on-load instead
- Set
data-resend-labelwhen the first action and the repeat action read differently. "Send code" on first use and "Resend" on every cycle after is clearer than leaving "Resend" on a button that has never been clicked - Keep countdown templates short. "Resend in 30s" fits; a full sentence does not. The label redraws every second
- Pair the built-in toast with a concrete confirmation message ("A new code has been sent to your mobile number.") rather than a generic "Success". Users need to know what succeeded
- For custom toast variants, positions, or multi-step actions, skip
data-sent-*and listen fornds:cooldown:triggered. Call NDS.Alert.create yourself with the full option set - Duration values under 5 seconds feel abrupt and may not give the toast time to be read. Duration values over 60 seconds should trigger a dedicated "please wait" screen, not a button label
Data Attributes
| Attribute | Description |
|---|---|
data-cooldown | Seconds to hold the cooldown. Required to opt in. Non-positive values skip the cooldown entirely. Read once at wire time; editing after page load has no effect |
data-cooldown-loading | Seconds to hold the loading state before the countdown begins. Default 0, which skips the loading phase. Read once at wire time |
data-cooldown-label | Countdown text template. {s} is replaced by the seconds remaining. Default {s} (number only) |
data-resend-label | Label to restore after the first completed cycle. Omit to keep the initial label across cycles. A mid-loading reset() always restores the initial label |
data-sent-title | Title of the success toast fired when the cooldown begins. Either this or data-sent-message must be present for a toast to appear |
data-sent-message | Description of the success toast fired when the cooldown begins. Toast uses variant success, position bottom, duration 4000ms |
Events
All events bubble and fire on the button element. Listen for them to add custom behavior (analytics, alternate toasts, parallel UI updates) without replacing the built-in flow.
| Event | Fires |
|---|---|
nds:cooldown:loading | Loading phase begins. Skipped when data-cooldown-loading is 0 or absent |
nds:cooldown:triggered | Loading ends and the cooldown starts. The built-in toast (if configured) fires right after this. Best hook for custom toasts or analytics |
nds:cooldown:tick | Every second during the cooldown. event.detail.remaining is the seconds left, including a first tick at the full duration |
nds:cooldown:end | Cooldown completed naturally or reset() was called. Button is re-enabled and the label is restored |
JavaScript API
The NDS.CooldownButton API provides programmatic control for dynamically added buttons and for aborting a cooldown in flight. Auto-initialization handles everything for static markup; no JS call is needed for the common case.