Passkey Name Prompt

Prefills a distinct name right after a passkey is created, so the security page isn't a list of identical "Passkey" rows nobody can tell apart.

StopsIdentical passkeys nobody can tell apart

Installation

pnpm dlx shadcn@latest add https://bollard-ui.vercel.app/r/passkey-name-prompt.json

The files land in your project. They're yours to edit.

Usage

passkey-name-prompt-usage.tsx
import { PasskeyNamePrompt } from "@/components/ui/passkey-name-prompt"

<PasskeyNamePrompt
  aaguid="adce0002-35bc-c60a-648b-0b25f1f05503"
  userAgent={MAC_CHROME}
  backupEligible={false}
  existingNames={["iCloud Keychain", "Mac · Chrome", "YubiKey 5C"]}
  defaultValue="Mac · Chrome"
  onSave={() => new Promise((resolve) => setTimeout(resolve, 700))}
  onSkip={() => {}}
/>

What it catches

  • Identical generic names ("Passkey")
  • Empty or duplicate names, with a one-tap fix
  • Provider from the AAGUID, or device from the user agent
  • Whether the passkey syncs (manager) or stays on this device

Props

PasskeyNamePrompt props
PropTypeDefault
aaguid

The new credential's AAGUID, from the registration response. Names syncing managers like iCloud Keychain or 1Password.

string—
userAgent

User agent to describe the device. Read from the browser after mount when omitted.

string—
attachment

`authenticatorAttachment` from the response: "cross-platform" without a known AAGUID reads as a security key.

"platform" | "cross-platform"—
transports

`response.getTransports()`. "hybrid" (made on a phone over a QR code) stops the name and notes describing this computer.

string[]—
backupEligible

The backup-eligible (BE) flag. true: it syncs; false: it lives on this device only. Omit when unknown.

boolean—
existingNames

Names of the user's other passkeys, to keep the new name distinct.

string[][]
providers

Extra AAGUID → provider entries, merged over the built-in table.

Record<string, PasskeyProvider>—
value

Controlled name. Defaults to the suggestion until the user edits it.

string—
defaultValue

Initial name when uncontrolled. Defaults to the suggestion.

string—
onValueChange

Called with the name on every change.

(value: string) => void—
onSave

Saves the name. May return a Promise; the button shows a pending state and a rejection shows an error.

(name: string) => void | Promise<void>—
onSkip

Shows a Skip button. Called with the suggestion so the passkey is never left as just "Passkey".

(suggestion: string) => void—
maxLength

Longest name allowed.

number64
delay

Pause in ms before warnings appear while typing.

number400
messages

Override any visible or announced text.

Partial<PasskeyNameMessages>—

* required. Also accepts the native props of <input> (className, aria-*, data-*…), passed through to it.

Accessibility

  • Visible label; input has aria-invalid when blocked and is described by a polite status
  • Blocking errors use role=alert
  • Icons are aria-hidden

Only what the source does. Test with your own screen reader and content before shipping.

Limitations

  • Display only, never a trust decision; AAGUIDs can be zeroed or spoofed
  • Built-in AAGUID table is small; pass providers for others
  • User-agent device names are best-effort

What it stops

Identical passkeys nobody can tell apart

Also known as

passkey · webauthn · security keys · aaguid · icloud keychain · 1password · security settings · biometrics

Field type

Dependencies

Pairs well with