@zkpassport/ui
v0.15.1
Published
Drop-in QR verification card for ZKPassport. Vanilla JS by default; React component available at @zkpassport/ui/react.
Downloads
680
Readme
ZKPassport UI
Drop-in QR verification card for ZKPassport. Mount once, get a verification flow with state transitions, retry, and result callbacks.
Installation
npm install @zkpassport/ui @zkpassport/sdkReact
import { ZKPassportQRCode } from "@zkpassport/ui/react"
export default function Page() {
return (
<ZKPassportQRCode
name="Aztec"
logo="https://aztec.com/logo.png"
purpose="Prove you are an adult from the EU but not from Scandinavia"
scope="age-check"
query={(queryBuilder) => queryBuilder.gte("age", 18).done()}
onResult={({ verified, result, uniqueIdentifier }) => {
if (verified) console.log(result, uniqueIdentifier)
}}
/>
)
}In Next.js App Router, the React entry is marked "use client", so importing from a server component yields a clear error.
Vanilla JS
Works the same in plain JS, Vue, Svelte, Solid, Astro, or any bundler-based stack:
import { mount } from "@zkpassport/ui"
const handle = mount(document.getElementById("zk-passport")!, {
name: "Aztec",
logo: "https://aztec.com/logo.png",
purpose: "Prove you are an adult from the EU but not from Scandinavia",
scope: "age-check",
query: (queryBuilder) => queryBuilder.gte("age", 18).done(),
onResult: ({ verified, result, uniqueIdentifier }) => {
if (verified) console.log(result, uniqueIdentifier)
},
})
// handle.update(nextOptions) — swap options
// handle.retry() — rebuild the request
// handle.unmount() — tear it all downCallbacks
All optional. The SDK lifecycle callbacks pass through verbatim — their signatures are derived from @zkpassport/sdk's QueryBuilderResult, so any SDK change flows through here automatically.
| Callback | Source | When |
| --- | --- | --- |
| onReady | UI | QR is scannable (fires once per request) |
| onRetryClicked | UI | User clicked the retry button after an error |
| onBridgeConnect | SDK | Bridge connected to the mobile app |
| onRequestReceived | SDK | Mobile app received the request payload |
| onGeneratingProof | SDK | User approved; proof generation started |
| onProofGenerated(proof) | SDK | A single proof has been generated |
| onResult(response) | SDK | Final result with { verified, uniqueIdentifier, result, ... } — check response.verified for pass/fail |
| onReject | SDK | User rejected on phone |
| onError(message) | SDK | An SDK-side error (message: string) |
Internal failures (request build failed, the
querycallback threw, QR generation failed) are logged to the console and transition the card to theerrorvisual state — they don't fireonError, which is reserved for SDK-emitted errors so its semantics match@zkpassport/sdkexactly.
Props
Props are a 1:1 mirror of sdk.request(...)'s argument shape, plus:
domain?— passed tonew ZKPassport(...). Defaults towindow.location.hostname.query(required) — receives the SDK'sQueryBuilder, applies gates and returnsqueryBuilder.done().- Lifecycle callbacks (see table above).
So name, logo, purpose, scope, mode, devMode, validity, uniqueIdentifierType, oprfKeyId are all valid props with their SDK-derived types. New SDK request fields appear automatically on the next SDK bump.
Excluded from the public surface (still accepted by the SDK if you call it yourself):
projectID— not consumed by the mobile app todaytopicOverride,keyPairOverride,cloudProverUrl,bridgeUrl— bridge plumbing for advanced/internal use
<ZKPassportQRCode
name="Aztec"
logo="https://aztec.com/logo.png"
purpose="Prove you are an adult"
scope="age-check"
devMode
mode="full"
validity={86_400}
query={(queryBuilder) => queryBuilder.gte("age", 18).done()}
/>CSS
Styles auto-inject as a <style> tag wrapped in @layer zkpassport, so host app styles in the default cascade always win. CSP-strict consumers can opt out of inline styles by importing the standalone bundle:
import "@zkpassport/ui/styles.css"How it works
- Rendering uses Preact (~3.5KB gzipped, bundled inline). React consumers don't drag Preact into their app tree — the card mounts into its own root inside a host
<div>. - Two entry points:
@zkpassport/ui(vanillamount()) and@zkpassport/ui/react(React component). Both call into the same Preact<Card>. - State machine lives in a
useCardhook: builds the request viasdk.request(...), subscribes to the SDK's bridge events (onBridgeConnect,onRequestReceived,onGeneratingProof,onResult,onReject,onError), and maps them to UI states (preparing → connecting → waiting → scanned → generating → success | error). queryreceives the SDK'sQueryBuilder. Apply gates and returnqueryBuilder.done(). Other props (name,logo,scope,devMode, …) flow straight through tosdk.request(...).- Retry rebuilds the request from scratch (re-runs
sdk.request(...)and thequerycallback); a cancellation token invalidates SDK event subscribers from the superseded request. - Assets (icons, QR logo, App Store / Google Play badges) are inline SVG strings so the package works with any bundler — no SVG/file loader needed.
- Bundle size: ~65KB raw, ~23KB gzipped. Roughly half is the
qrcodelibrary; the rest is Preact runtime + our code + inline SVGs.
License
Apache-2.0
