OTP Field
One-time passcode entry composed on top of InputOTP. Adds a countdown to expiry, a resend button with cooldown, a multi-channel switcher (Email / WhatsApp / SMS), and an error state for the TpEMIS dual-identity verification flow.
Default — phone OTP
Five-minute expiry, channel switch, resend cooldown of 60 seconds. Try changing the channel; the masked target stays the same so the applicant can switch delivery without re-entering their number.
(none)Resend count: 0Error state
Pre-filled with an incorrect code and an error message. Slots get a destructive border; the error renders inside a role="alert" so screen readers announce it.
Single channel
Omit onChannelChange (or pass a one-element channels array) to render the field without the channel switcher.
SSG-safety
The countdown reads Date.now(), which differs between prerender and hydration. OtpField only reads time inside a useEffect tick; the initial render emits a deterministic empty timer so the prerendered HTML matches what hydration produces. Pass expiresAt via state set inside useEffectrather than at module / render time.
Accessibility
- Inherits the OTP input's native one-character-per-slot keyboard nav.
- The ticking timer sits in an
aria-live="off"node — second-by-second changes stay silent so screen readers aren't spammed. Only the one-shot expiry state flips into arole="status"region. - Errors render inside
role="alert"for instant SR announcement. - The channel switcher button carries an explicit
aria-label. - The field itself carries
aria-invalidwhenerroris set; slots get a destructive border in the same state.