> For the complete documentation index, see [llms.txt](https://docs.sigilvault.xyz/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.sigilvault.xyz/typescript/jazz-react.md).

# @sigil/jazz-react

Drop-in React widget for the Sigil pair handshake. Renders a QR code (desktop UA) or a tap-to-deeplink button (mobile UA), reflects every lifecycle state via `data-state="…"`, and surfaces typed callbacks. Builds on top of the headless [`@sigil/astrotrain`](/typescript/astrotrain-ts.md) SDK.

## Install

```bash
pnpm add @sigil/astrotrain @sigil/jazz-react
```

## Minimum example

```tsx
import { JazzPair } from "@sigil/jazz-react";
import "@sigil/jazz-react/theme.css"; // optional default look

export function PairCard() {
  return (
    <JazzPair
      octaneUrl="/api"
      apiKey="proxied-server-side"
      createSession={async () => {
        const res = await fetch("/api/create-pair-session", { method: "POST" });
        return res.json();
      }}
      onComplete={(pair) => {
        // pair.pair_id is the durable id to persist.
        // pair.bindings = [{ chain, wallet_index, access }, ...]
      }}
      onReject={(err) => /* err.reason */}
      onExpire={() => /* TTL elapsed */}
      onError={(err) => /* "network" | "http" | "deserialise" */}
    />
  );
}
```

The widget creates the session via your callback (the API key never leaves your server), polls for the result, and fires the right callback once the user resolves it on the appliance.

## Props

| Prop             | Type                            | Notes                                                                                                      |
| ---------------- | ------------------------------- | ---------------------------------------------------------------------------------------------------------- |
| `octaneUrl`      | `string`                        | Octane base URL — typically a `/api/*` path proxied to your backend so the API key stays server-side.      |
| `apiKey`         | `string`                        | Bearer auth — pass a placeholder (`"proxied-server-side"`) when your proxy adds the real header.           |
| `session`        | `PairSession`                   | Pre-created session. Mutually exclusive with `createSession`.                                              |
| `createSession`  | `() => Promise<PairSession>`    | Callback the widget invokes on mount to mint a session. Use this when the API key has to stay server-side. |
| `mode`           | `"auto" \| "qr" \| "deeplink"`  | Default `auto` — picks `deeplink` on mobile UA, `qr` otherwise.                                            |
| `validationCode` | `string`                        | Six-digit code to bake into the URL on single-device flows. Triggers `&v=…` on the deeplink.               |
| `qrSize`         | `number`                        | QR pixel size, default `240`.                                                                              |
| `title`          | `string`                        | Heading. Default `"Pair with Sigil"`.                                                                      |
| `subtitle`       | `string`                        | Optional subtitle below the heading.                                                                       |
| `className`      | `string`                        | Merged onto the `.jazz-pair` root.                                                                         |
| `onComplete`     | `(pair: CompletedPair) => void` | Fires on user approval.                                                                                    |
| `onReject`       | `(err: PairError) => void`      | Fires when the user declines on the appliance.                                                             |
| `onExpire`       | `(err: PairError) => void`      | Fires when the TTL elapses before approval.                                                                |
| `onError`        | `(err: PairError) => void`      | Fires for every other failure.                                                                             |
| `fetch`          | `typeof fetch`                  | Optional override (tests, edge runtimes).                                                                  |
| `userAgent`      | `string \| null`                | Optional UA override (tests, SSR).                                                                         |

## Lifecycle states

The current state is exposed on `[data-state="…"]` for both the root element and the status pill, so host CSS can theme each leaf:

* `loading` — `createSession` is in flight
* `pending` — session in hand, awaiting user approval
* `completed` — bindings shown
* `rejected` — user declined
* `expired` — TTL elapsed
* `error` — every other failure (network / http / deserialise)

## Single-device support

When `validationCode` is supplied, the widget renders a Universal Link (`https://sigilvault.xyz/pair?session=…&v=…`) instead of the bare session URI. Sunstorm reads the code from the URL and prefills its approval card — the user never has to retype it.

Combined with a `returnUrl` on the SP backend, this gives a full single-device pair loop: tap → approve → bounce back. See [Single-device flow](/single-device.md) for end-to-end details.

```tsx
const [code, setCode] = useState<string | null>(null);

<JazzPair
  octaneUrl="/api"
  apiKey="proxied"
  createSession={async () => {
    const r = await fetch("/api/pair", { method: "POST" }).then((x) => x.json());
    setCode(r.validation_code);    // your backend returns this alongside session_id
    return r;
  }}
  {...(code ? { validationCode: code } : {})}
  onComplete={(p) => persist(p)}
/>
```

## Visibility-change resume

The widget listens for `document.visibilitychange` and re-issues the result long-poll when its tab returns to the foreground. Mobile Safari suspends background tabs aggressively — without this, returning from Sunstorm to the SP page after approval would leave the widget hanging in `pending`. You don't need to do anything to opt in.

## Theme

`@sigil/jazz-react/theme.css` is opt-in. It scopes its design tokens (palette, type, radii) to `.jazz-pair`, mirroring Sunstorm's look so partner sites read as consistent. Hosts with their own design system can skip the import and override the `--jazz-*` variables.

## Re-exports from `@sigil/astrotrain`

For convenience, the package re-exports the headless types and functions you might need alongside the widget — `PairError`, `createPairSession`, `awaitPairResult`, the lifecycle event types, etc. You can pull everything from a single import root:

```ts
import { JazzPair, awaitPairResult, type PairError } from "@sigil/jazz-react";
```


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.sigilvault.xyz/typescript/jazz-react.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
