KQ-533 · EXPIRY/RING
Expiry Ring
A countdown ring that drains linearly — the one place a tween is more honest than a spring, because no easing may borrow from a second. Inside it mm:ss rolls on snap, one wheel per digit so only the place that changed moves; under the warn threshold the ring pulses on drift and the digits warm to warn, and zero stamps Expired on recoil and swaps the line for a Resend button that restarts the run. The element is a timer with its live region off so a screen reader is not read a number every second, a polite region speaks only at the two thresholds, and Resend is a real button; under reduced motion the pulse never runs while the ring still drains.
Waylight · 30s left · sent 1×
Install
One command — the source lands in your repo. Or copy it from the Code tab.
$ pnpm dlx shadcn@latest add @kinetiq/expiry-ring
Props
- seconds
- number
- default —
- Length of the countdown.
- running
- boolean
- default true
- Pause and resume. Pausing keeps the elapsed time, so resuming carries on rather than restarting.
- warnAt
- number
- default 10
- Seconds left when the ring starts pulsing and the digits warm to warn.
- onExpire
- () => void
- default —
- Fires once, from the tick that reaches zero.
- onResend
- () => void
- default —
- Fires from the Resend button, which also restarts the countdown.
- onTick
- (secondsLeft: number) => void
- default —
- Whole seconds left, emitted from the tick that changed the number.
- label
- string
- default —
- What is expiring. Names the timer and both threshold announcements.
| Prop | Type | Default | Description |
|---|---|---|---|
| seconds | number | — | Length of the countdown. |
| running | boolean | true | Pause and resume. Pausing keeps the elapsed time, so resuming carries on rather than restarting. |
| warnAt | number | 10 | Seconds left when the ring starts pulsing and the digits warm to warn. |
| onExpire | () => void | — | Fires once, from the tick that reaches zero. |
| onResend | () => void | — | Fires from the Resend button, which also restarts the countdown. |
| onTick | (secondsLeft: number) => void | — | Whole seconds left, emitted from the tick that changed the number. |
| label | string | — | What is expiring. Names the timer and both threshold announcements. |