Treat paste as the primary path: when a code is pasted into any box, strip spaces and non-digits (value.replace(/\D/g, "")) and distribute the digits across all six boxes at once.
Auto-advance focus as each digit lands, and make backspace on an empty box jump back to the previous one and clear it — so correcting a typo never traps the cursor.
Model the field as one string, not six independent values. useState("847291") beats useState(["","","","","",""]); the boxes are just a view of a single source of truth.
On mobile, wire up inputmode="numeric" and autocomplete="one-time-code" so the OS surfaces the SMS code as a one-tap autofill above the keypad.
Throttle resend behind a visible 30s countdown. Without it, impatient users spam the button, hit 429 Too Many Requests, and get temporarily banned by the server.
Give instant feedback on submit: a wrong code shakes and clears back to focus, a correct code locks each box green with a check and a 'Verified' state.
Do / Don't
Do: Store the full code as a single string and render the six boxes as a view of it
Do: Strip non-digits from pasted input and spread the code across every box automatically
Do: Gate the resend button behind a visible countdown timer to avoid rate-limit bans
Don't: Rely on one wide input — pasted codes with spaces overflow and choke it
Don't: Leave a wrong code sitting silently — shake, clear, and refocus instead
The Design System Blueprint
An 11-page visual guide to consistent, scalable UI: colors, typography, spacing, components, motion.