KQ-1161 · TUMBLER/CODE
Tumbler Code
A one-time-code field drawn as a pin-tumbler lock: each digit raises its spring-loaded pin stack on snap to a height set by the digit, the coil compressing above it, and a paste raises them left to right on a cascade. When check accepts the code every split glides onto the shear line and the plug turns a quarter on snap with a clunk, folding the key pins away; a refused code jiggles the pins on a tween and drops them on recoil so they bounce as they land. The cells are real inputs in one tab stop: digits type in and advance, Backspace and Delete remove, arrows and Home/End move, and paste or one-time-code autofill fills them all.
Coldbrook Bank texted 408 615 to •• 42.
pins set 0 of 6 · waiting for the rest
Install
One command — the source lands in your repo. Or copy it from the Code tab.
$ pnpm dlx shadcn@latest add @kinetiq/tumbler-code
Props
- label
- string
- default —
- What the code is for: the visible label and the name of the group of cells.
- value / defaultValue
- string
- default ""
- Controlled or initial code: digits only, never longer than length.
- onValueChange
- (value: string) => void
- default —
- Fires from the key, paste or refusal that changed the code, with the new digits.
- length
- number
- default 6
- How many digits, and so how many pins the cylinder has, 4 to 8.
- brass
- "brass" | "nickel" | "black"
- default "brass"
- The metal of the housing, the plug and the face. The pins stay nickel over brass so the split always reads.
- verify
- boolean
- default true
- Check the code when its last digit lands and turn or refuse the lock. Needs check; off, the field only collects the code.
- check
- (code: string) => boolean | Promise<boolean>
- default —
- The host's verifier. A promise holds the plug in a test turn until it settles; a rejected one leaves the pins where they are.
- onComplete
- (code: string) => void
- default —
- Fires from the key or paste that filled the last cell, with the whole code.
- onResult
- (accepted: boolean, code: string) => void
- default —
- Fires once check has answered, before the lock turns or the pins drop.
- name
- string
- default —
- The form field name; a hidden input carries the code under it.
- hint
- string
- default —
- Helper text under the cells, wired to every cell with aria-describedby.
- error
- string | null
- default —
- What is wrong, from the host: shown under the cells, marked on them with aria-invalid, and announced once.
- wrongText
- string
- default "That code didn't turn the lock. Try again."
- What a refused code says.
- required
- boolean
- default false
- Marks every cell required, so a form will not submit with an empty one.
- sound
- boolean
- default false
- Play each pin's click, the plug's clunk and the refusal's shrug. Nothing plays, and no audio context exists, unless this is on; sounds only ever answer the visitor.
- disabled
- boolean
- default false
- Disables the field and dims it.
- className
- string
- default —
- Merged onto the root element's classes.
| Prop | Type | Default | Description |
|---|---|---|---|
| label | string | — | What the code is for: the visible label and the name of the group of cells. |
| value / defaultValue | string | "" | Controlled or initial code: digits only, never longer than length. |
| onValueChange | (value: string) => void | — | Fires from the key, paste or refusal that changed the code, with the new digits. |
| length | number | 6 | How many digits, and so how many pins the cylinder has, 4 to 8. |
| brass | "brass" | "nickel" | "black" | "brass" | The metal of the housing, the plug and the face. The pins stay nickel over brass so the split always reads. |
| verify | boolean | true | Check the code when its last digit lands and turn or refuse the lock. Needs check; off, the field only collects the code. |
| check | (code: string) => boolean | Promise<boolean> | — | The host's verifier. A promise holds the plug in a test turn until it settles; a rejected one leaves the pins where they are. |
| onComplete | (code: string) => void | — | Fires from the key or paste that filled the last cell, with the whole code. |
| onResult | (accepted: boolean, code: string) => void | — | Fires once check has answered, before the lock turns or the pins drop. |
| name | string | — | The form field name; a hidden input carries the code under it. |
| hint | string | — | Helper text under the cells, wired to every cell with aria-describedby. |
| error | string | null | — | What is wrong, from the host: shown under the cells, marked on them with aria-invalid, and announced once. |
| wrongText | string | "That code didn't turn the lock. Try again." | What a refused code says. |
| required | boolean | false | Marks every cell required, so a form will not submit with an empty one. |
| sound | boolean | false | Play each pin's click, the plug's clunk and the refusal's shrug. Nothing plays, and no audio context exists, unless this is on; sounds only ever answer the visitor. |
| disabled | boolean | false | Disables the field and dims it. |
| className | string | — | Merged onto the root element's classes. |