# ElvixAppPasskeys

> Per-app passkey manager. Lists and lets the user add or remove WebAuthn passkeys scoped to ONE application — the credential can only sign them in to this app. Account-level passkeys (added on `/account/security`) work everywhere and are NOT shown here.

<!-- app/account/apps/[appId]/page.tsx -->
```tsx
import {
  ElvixAppPasskeys,
  ElvixCard,
  ElvixProvider,
} from "@elvix.is/sdk/react";

// /account/apps/[appId]/page.tsx — host renders one ElvixAppPasskeys
// per app pane. The provider applies the app's brand color via CSS
// vars so the add button + row icons match the app brand chord.
export default async function AccountAppPage({ params }: { params: { appId: string } }) {
  const { appId } = await params;
  // ... fetch the app + brand ...
  return (
    <ElvixProvider theme="light" brand={appBrand} baseUrl="">
            <ElvixAppPasskeys
        appId={appId}
        appName={app.name}
        onResult={(r) => {
          if (r.ok && r.kind === "added") console.log("registered");
          if (r.ok && r.kind === "removed") console.log("removed", r.passkeyId);
        }}
      />
    </ElvixProvider>
  );
}
```

## Props

| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `appId` | `string` | optional | from `<ElvixProvider>` bootstrap | Database id of the application this card lists passkeys for. Falls back to the app id surfaced by `<ElvixProvider clientId>`'s bootstrap envelope. |
| `appName` | `string` | optional | from `<ElvixProvider>` bootstrap | Display name shown in the heading, subtitle, and "Add a passkey for X" CTA. Overrides the bootstrap-derived name (handy on first-party elvix pages that already have the app row in scope). |
| `onResult` | `(r: ElvixAppPasskeysResult) => void` | optional | none | Terminal callback. Fires for both add (`{ ok: true, kind: "added" }`) and remove (`{ ok: true, kind: "removed", passkeyId }`) success states, plus typed failures. ResponseDto-shape. |
| `onAdded` | `() => void` | optional | none | Shortcut fired ONLY when a passkey successfully registers. Hosts that need to call `router.refresh()` for their own server-rendered list wire this. |
| `onRemoved` | `(passkeyId: string) => void` | optional | none | Shortcut fired ONLY when a passkey is successfully removed. Symmetric with `onAdded`. |
| `className` | `string` | optional | `""` | Extra classes merged onto the outer `elvix-card`. |
| `width` / `height` / etc. | size props | optional | auto | Standard `<Elvix*>` size knobs. See [`ElvixSizeProps`](/docs/sdk/types.md). |

> **Note:** **App-scoped vs account-level.** Passkeys with `applicationId=null` (added on `/account/security`) sign the user in to every app they belong to. Passkeys registered through `<ElvixAppPasskeys>` are bound to one app's id by the elvix `register/start` challenge — picking the credential on another app's sign-in screen returns `credential_not_for_app`. The WebAuthn RP (`elvix.is`) is identical for both flavors; the scope is an elvix-level policy filter.

> **Note:** **Brand chord.** The add button and row icons paint from `var(--elvix-primary)` installed by `<ElvixProvider brand>`. Mount the provider with the per-app brand colors (e.g. via the elvix `resolveBrandColors` helper on first-party pages) so the card feels like part of that app, not the elvix shell.

> **Note:** **Endpoints used.** `GET /api/account/apps/<appId>/passkeys` for the list, `DELETE /api/account/apps/<appId>/passkeys?passkeyId=<id>` for removal, and the standard `/api/auth/passkey/register/start` + `/finish` with an extra `applicationId` body field for registration. All three require the signed-in `account` session and a non-deleted membership in the named app.

> **Warning:** **Cross-origin support.** Same architecture as the rest of the SDK: the cross-origin interceptor attaches `Authorization: Bearer <token>` and rewrites `/api/account/...` calls to `https://elvix.is/...` automatically when the host is on a customer origin. Customers who whitelist their origin in elvix's Console and mount `<ElvixProvider>` get the full per-app passkey UX without any extra wiring.

## Result shape

```ts
type ElvixAppPasskeysResult =
  | { ok: true; kind: "added" }
  | { ok: true; kind: "removed"; passkeyId: string }
  | { ok: false; error: string; message?: string };
```


## Related

- [ElvixSignInForm](/docs/components/elvix-sign-in-form.md): The full branded sign-in surface (card, header, brand wash, every Console-enabled method, Secured-by-elvix badge). Exported as both `ElvixSignInForm` and the short alias `ElvixSignIn` — same component.
- [ElvixSignOutButton](/docs/components/elvix-sign-out-button.md): Symmetric counterpart of `<ElvixSignInButton>`. One click invalidates the elvix session server-side, clears the SDK's local token, drops the `elvix_token` cookie, then navigates.
- [ElvixSessions](/docs/components/elvix-sessions.md): Live session list scoped per-app or per-account, with per-row revoke and a two-CTA mass-revoke confirm pane.
