KQ-1706 · PASSKEY/ENROL
Passkey Enrol
A passkey setup step: a key inside a disc with rings round it, a short explanation, Create, and the devices already enrolled. While the platform prompt is open the rings draw slowly one after another on a symmetric tween and then turn very slowly, only in view and in a visible tab; success contracts every ring onto the key on springs.glide, outer ones a beat behind, into a solid mark that settles on springs.snap with a check drawn on springs.flick, while the new device slides into the list from 8px on glide with a New chip landing on springs.recoil. Rename edits in place (Enter saves, Escape cancels), Remove asks in the row, and cancelled prompts and unsupported browsers each get a plain message and a fallback.
2 passkeys · password can retire
Install
One command — the source lands in your repo. Or copy it from the Code tab.
$ pnpm dlx shadcn@latest add @kinetiq/passkey-enrol
Props
- account
- PasskeyAccount
- default defaultPasskeyAccount
- Whose passkeys these are: { name, email }.
- devices / defaultDevices
- PasskeyDevice[]
- default — / defaultPasskeyDevices
- The enrolled devices: { id, name, kind, addedAt, lastUsedAt? }, controlled or initial.
- onDevicesChange
- (devices) => void
- default —
- Fires with the whole list after an enrol, a rename or a remove.
- onEnrol
- () => PasskeyDevice | Promise<PasskeyDevice>
- default —
- Opens the platform prompt; resolve with the new device, reject with { reason: "cancelled" | "unsupported" | "error" } or the platform's NotAllowedError.
- onRename
- (device, name) => void
- default —
- A device was renamed.
- onRemove
- (device) => void
- default —
- A device was removed after its confirm.
- onFallback
- (kind: "other-device" | "password") => void
- default —
- A fallback was chosen.
- supported
- boolean
- default true
- Whether this browser can create passkeys; false shows the unsupported message from the start.
- now
- number
- default defaultPasskeyNow
- The moment "used today" counts from, in ms.
- ringCount
- number
- default 3
- How many rings draw round the key, 2 to 5.
- drawDuration
- number
- default 1400
- Milliseconds each ring takes to draw while the prompt is open.
- title
- string
- default "Add a passkey"
- The heading.
- status
- "ready" | "loading" | "error"
- default "ready"
- Whether the device list has arrived.
- onRetry
- () => void
- default —
- Try again was pressed after the list failed to load.
- ringStyle
- "rings" | "arcs" | "dots"
- default "rings"
- The rings: full circles, broken arcs that turn both ways, or rings of dots.
- density
- "compact" | "cozy" | "roomy"
- default "cozy"
- Padding, row height and type size.
- accent
- "cobalt" | "mint" | "amber" | "ink" | string
- default "cobalt"
- The rings, the solid mark and Create: a named pigment or any CSS colour.
- formatDate
- (ms) => string
- default "3 Sep" (UTC)
- How a date is written.
- maxHeight
- number
- default 560
- The surface's height cap in px; the list scrolls inside it.
- disabled
- boolean
- default false
- Nothing can be enrolled, renamed or removed.
- className
- string
- default —
- Classes for the surface.
| Prop | Type | Default | Description |
|---|---|---|---|
| account | PasskeyAccount | defaultPasskeyAccount | Whose passkeys these are: { name, email }. |
| devices / defaultDevices | PasskeyDevice[] | — / defaultPasskeyDevices | The enrolled devices: { id, name, kind, addedAt, lastUsedAt? }, controlled or initial. |
| onDevicesChange | (devices) => void | — | Fires with the whole list after an enrol, a rename or a remove. |
| onEnrol | () => PasskeyDevice | Promise<PasskeyDevice> | — | Opens the platform prompt; resolve with the new device, reject with { reason: "cancelled" | "unsupported" | "error" } or the platform's NotAllowedError. |
| onRename | (device, name) => void | — | A device was renamed. |
| onRemove | (device) => void | — | A device was removed after its confirm. |
| onFallback | (kind: "other-device" | "password") => void | — | A fallback was chosen. |
| supported | boolean | true | Whether this browser can create passkeys; false shows the unsupported message from the start. |
| now | number | defaultPasskeyNow | The moment "used today" counts from, in ms. |
| ringCount | number | 3 | How many rings draw round the key, 2 to 5. |
| drawDuration | number | 1400 | Milliseconds each ring takes to draw while the prompt is open. |
| title | string | "Add a passkey" | The heading. |
| status | "ready" | "loading" | "error" | "ready" | Whether the device list has arrived. |
| onRetry | () => void | — | Try again was pressed after the list failed to load. |
| ringStyle | "rings" | "arcs" | "dots" | "rings" | The rings: full circles, broken arcs that turn both ways, or rings of dots. |
| density | "compact" | "cozy" | "roomy" | "cozy" | Padding, row height and type size. |
| accent | "cobalt" | "mint" | "amber" | "ink" | string | "cobalt" | The rings, the solid mark and Create: a named pigment or any CSS colour. |
| formatDate | (ms) => string | "3 Sep" (UTC) | How a date is written. |
| maxHeight | number | 560 | The surface's height cap in px; the list scrolls inside it. |
| disabled | boolean | false | Nothing can be enrolled, renamed or removed. |
| className | string | — | Classes for the surface. |