PasskeyManagerbeta
Lists a user's registered passkeys (WebAuthn credentials) and lets them register a new one, rename one in place, or delete one.
01 Usage
Basic
Passkeys
<script lang="ts">
import { PasskeyManager } from '@urbicon-ui/auth';
</script>
<PasskeyManager apiPath="/api/auth/passkey" />
02 Accessibility
Every row button says which passkey it acts on
The visible text is just "Rename" or "Delete", but each button carries aria-label with the passkey's name appended. A reader
tabbing the list hears "Delete — MacBook Pro" rather than four identical buttons, and because
the accessible name still begins with the visible word, voice control ("click Delete") keeps
working.
Renaming keeps the keyboard where it was
"Rename" swaps the row's name for a labelled text field and moves focus into it with the
current name selected, so typing replaces it. Enter or "Save" commits; Escape or "Cancel" restores the stored name. The form
handles Escape, not the field, so it still cancels once focus has moved on to "Save", and
it does not bubble — the panel can sit inside a dialog without the rename form and the
dialog closing together. Focus returns to that row's "Rename" button either way. While a
rename is being saved the field goes readonly rather than disabled: a disabled element loses focus to the
document body, which would strand a keyboard user at the top of the page for the length of
the request, and permanently if the server refuses. A refused rename keeps the field open,
focused, with the draft intact.
Errors announce in place
WebAuthn failures (a cancelled prompt, an unsupported authenticator, a rejected
registration) land in the always-mounted role="alert" region above the list, so the outcome
of a flow that ran inside the browser's own credential UI is announced back on the page. A
completed rename reports through the polite role="status" region beside it — the row's own text changes
without a word otherwise, and a finished rename is not worth interrupting for.
The list is a list
Registered passkeys render as <ul> / <li> with the name and the creation/last-used
dates as text. The "Add passkey" button is disabled with aria-busy while registration runs.
The credential prompt is the browser’s
Once registration starts, the platform takes over with its own dialog for Touch ID, Windows Hello or a security key. That dialog's accessibility belongs to the operating system, not this component; what comes back from it is announced through the live region.
03 API Reference
Prop | Type | Default | Description | |
|---|---|---|---|---|
apiPath | string | '/api/auth/passkey' | API base path for passkey operations. | |
class | string | — | Extra classes on the root element. | |
csrf | CsrfClientOptions | — | CSRF cookie/header names. Only needed when the server overrides the defaults via config.csrf. Mutating requests echo the token automatically. | |
fetcher | typeof globalThis.fetch | — | Custom fetch implementation for all API calls. Defaults to the global fetch. Useful for mock backends in demos/tests or custom retry/auth layers. | |
preset | string | — | Apply a named preset registered via <BlocksProvider presets={{ PasskeyManager: { … } }}>.
Resolves after the provider defaults and before this instance's own
slotClasses, so a project-wide look lives in one place instead of being
repeated at every usage site. | |
slotClasses | Partial<Record<'root' | 'title' | 'error' | 'success' | 'list' | 'item' | 'empty' | 'renameForm' | 'renameField', string>> | — | Per-slot class overrides. | |
t | PartialAuthLocale | — | Locale overrides, deep-merged over the bundle registered for the active
locale — English unless registerAuthLocale ran for it. Pass any subset,
from a single string to a whole tree. | |
unstyled | boolean | — | Strip all default styling. |
04 Types
Local type definitions used by this component.
Name | Kind | Category | Used by | Description | |
|---|---|---|---|---|---|
PasskeyManagerProps | interface | props | 0 | — | |
PartialAuthLocale | type | helper | 1 | Consumer-facing locale input: any subset of AuthLocale. Components
accept this as their t prop and deep-merge it over the bundle registered
for the active locale — English unless registerAuthLocale ran for it — so
overriding a single string never silently blanks the rest. | |
CsrfClientOptions | interface | helper | 1 | — | |
DeepPartial | type | helper | 0 | Recursive partial: every branch and leaf becomes optional. | |
AuthLocale | interface | helper | 0 | The complete auth locale bundle. Every key is required: the bundles this
package ships (en, de) satisfy the full shape, and consumer overrides
enter as PartialAuthLocale, deep-merged by mergeAuthLocale over the
bundle registered for the active locale — English unless registerAuthLocale
ran for it — so component markup reads keys directly, without per-key
?? '…' fallback literals.
Placeholder convention: dynamic values use **single-brace** tokens
({n}, {name}, {email}) that the consuming component substitutes itself
via String.replace('{token}', value). There is deliberately **no**
{{…}} runtime interpolator in this package. The key-based translator twin
(authT/at) was removed, so {{…}} here would render verbatim. |
05 Installation
Import
import { PasskeyManager } from '@urbicon-ui/auth';