Micro-interactions
Passcode
A one-time-code field whose cells answer every keystroke: each character lands compressed and springs back, a paste distributes across the row rather than filling it on one frame, a wrong code shakes and clears itself, and a right one folds the whole row into a tick. Underneath there is exactly one real input holding the whole code — which is what keeps SMS autofill, password managers, select-all and a screen reader working, none of which survive six inputs with maxLength one.
Install
bun add motion clsx tailwind-mergeThen copy 2 files into your project:
- components/registry/passcode/passcode.tsx
- lib/utils.ts
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| length | number | 6 | Cells in the code. Six is the industry default because it is about the longest string most people can hold in working memory between reading it and typing it — the control exists so you can feel where that stops being true. |
| mode | "numeric" | "alphanumeric" | "numeric" | What the field accepts. Numeric also asks for the number pad, which matters far more on a phone than the validation does — an alphanumeric code field that opens a full keyboard costs the user two taps every single time. |
| mask | boolean | false | Render dots instead of characters. Off by default on purpose: a one-time code is worthless a minute after it arrives, and masking it only stops the person entitled to read it from checking their own typing. |
| stiffness | number | 520 | Spring tension of the per-character pop. High enough to land inside the gap between two fast keystrokes, or the cells queue up behind a quick typist and the feedback arrives after the character it is confirming. |
| damping | number | 26 | Spring friction. Low rings past and settles, which is the difference between a cell that acknowledges a keystroke and one that merely updates. |
| anticipation | number | 0.12 | How far a cell compresses before it springs back. A brief gather in the opposite direction is what makes the pop read as the cell reacting rather than as the cell being resized — at zero it is a plain overshoot and noticeably flatter. |
| pasteStagger | number | 45 | Delay between cells as a pasted or autofilled code distributes. Zero fills every cell on one frame, which is honest and reads as a glitch; the stagger turns an instantaneous state change into something the eye can follow. |
| shakeAmplitude | number | 10 | Lateral travel of the rejection. It moves the whole row rather than the cells individually — a row that shakes has been judged, a set of cells that each shake looks broken. |
| autoSubmit | boolean | true | Judge the code the moment the last cell fills instead of waiting for a button. Correct when a wrong answer costs nothing; wrong the instant it does. |
| submitDelay | number | 220 | Pause between the last character landing and the verdict. It exists so the final cell finishes its own pop before the row is judged — validating on the same frame steals the confirmation of the keystroke that caused it, and the user never learns their last character registered. |
| accentColor | string (hex) | "#a855f7" | The focused cell's ring, the filled border, the caret, and the success tick. |
| errorColor | string (hex) | "#f87171" | The rejection. It takes the cell borders rather than flooding the fills, so the code you typed stays readable while you are being told it is wrong. |
What It Demonstrates
- Spring
- Motion driven by physics (tension, mass, damping) rather than a set duration.
- Stiffness / Tension
- How strongly the spring pulls toward its target. Higher feels snappier.
- Anticipation
- A small wind-up in the opposite direction before a move, hinting at what's about to happen.
- Scale
- Make an element bigger or smaller.
- Pop in
- Element appears with a slight overshoot, like it bounces into place.
- Stagger
- Animate several items one after another with a small delay between each, creating a cascade.
- Shake / Wiggle
- A quick side-to-side jitter signaling an error or rejected input.
- Morph
- One shape smoothly turns into another shape, e.g. Dynamic Island.
Related Components
Stagger Menu
A menu overlay that blooms open as a clip-path circle spring while items stagger in with blur and rotation, then collapses back to its pill.
Tether
A popover that grows out of the corner nearest its trigger, and moves that origin when it flips.
Dial
A machined rotary knob: angular drag with detents you can feel on the way past, inertia on a flick, and a spring that settles into the nearest stop. The angle lives in a motion value rather than in React state — a drag writes it sixty times a second and a spring more often than that — so the only thing React holds is the detent index, and only when that integer actually changes. Arrow keys step through the same spring the hand does, so the keyboard is not a second, worse path to the same control.
Animated Hamburger
A three-bar hamburger that morphs to an X, driven by a toggle prop.
Morph Dialog
A dialog that morphs open from its trigger via shared-layout animation, with elements that travel between card and panel.
Blueprint Card
A technical-drawing card — dashed envelope, accent rules, corner nodes, and a soft glow that draw in on mount.