@nikpnevmatikos/pass-designer
v0.1.0
Published
React & React Native components to design and live-preview Apple Wallet & Google Wallet passes.
Maintainers
Readme
@nikpnevmatikos/pass-designer
React and React Native components to design and live-preview Apple Wallet & Google Wallet passes — one package, both platforms, same import.
Give non-technical users a friendly editor that shows a realistic pass card as they type, or drop a read-only preview into your dashboard. The designer produces a WalletPass design object you sign on your server with @nikpnevmatikos/pass-generator.
Presentational only — no secrets, no backend, no signing. That's what makes it safe to ship in any client.
Features
- 🎫 Five pass types: event ticket, boarding pass, coupon/offer, loyalty card, ID/generic
- 👀 Live preview of both wallets — Apple card and Google card, structurally faithful (colored card, floating white barcode plate, hero images)
- 📱 React Native support built in — Metro picks the native views automatically; phone-first layout (Preview/Edit tabs, collapsible sections, live peek bar)
- 🧩 Configurable: restrict pass types, wallets, hide or lock any field
- 📤 Export: design JSON download / OS share sheet, plus a copy-paste code snippet for signing
- 🔌 Zero runtime dependencies — barcode rendering, image picking, and date picking are delegated via props
- 🎨 Themeable via CSS variables (web); self-injecting scoped styles, no CSS import
Install
npm install @nikpnevmatikos/pass-designerReact ≥18 is a peer dependency. For React Native also install the optional peer:
npx expo install react-native-svg # barcode renderingQuick start (web)
import { PassDesigner, type WalletPass } from "@nikpnevmatikos/pass-designer";
export function Designer() {
return (
<PassDesigner
onChange={(pass: WalletPass) => console.log("live design", pass)}
onExport={(pass) => saveDesign(pass)} // optional — defaults to a JSON download
/>
);
}The user picks a pass type, fills friendly fields (details, branding, colors, images, barcode, back-of-pass rows), and watches the Apple/Google preview update live. Styles inject automatically — no CSS import.
Just the preview
PassPreview renders a pass card read-only — for dashboards, order confirmations, or docs:
import { PassPreview } from "@nikpnevmatikos/pass-designer";
<PassPreview pass={pass} wallet="google" />Quick start (React Native / Expo)
Same import — Metro resolves the native implementation automatically:
import * as ImagePicker from "expo-image-picker";
import { PassDesigner } from "@nikpnevmatikos/pass-designer";
<PassDesigner
onChange={setPass}
onPickImage={async () => {
const r = await ImagePicker.launchImageLibraryAsync({ mediaTypes: ["images"] });
return r.canceled ? null : r.assets[0]?.uri ?? null;
}}
/>The native designer is laid out for phones: a Preview / Edit tab pair keeps the card one tap away, a color-reflecting peek bar shows branding while editing, and the form uses collapsible sections.
Platform notes:
onPickImageis required to enable image uploads on native (upload buttons are hidden without it). It also works on web, replacing the default file input.onPickDate(optional): provide it and date fields become tappable picker rows — wire it to@react-native-community/datetimepickeror any picker. Without it, dates are text inputs.- Export buttons open the OS share sheet with the design JSON / snippet (or use
onExport). - Colors use preset swatches + a hex field.
A runnable demo lives in example-expo/ — npm install && npx expo start.
Props
<PassDesigner>
| Prop | Type | Default | Notes |
| --- | --- | --- | --- |
| initialPass | WalletPass | sample event ticket | Starting design |
| kinds | PassKind[] | all 5 | Which types are offered; a single value hides the picker |
| wallets | ("apple" \| "google")[] | both | Which previews are available; a single value hides the toggle |
| hiddenFields | string[] | — | Field keys to hide, e.g. ["seat.row", "images.strip"] |
| lockedFields | string[] | — | Field keys shown read-only |
| onChange | (pass) => void | — | Fires with the live pass on every edit |
| onExport | (pass) => void | JSON download / share sheet | Replaces the default export action |
| onPickImage | () => Promise<string \| null> | file input (web) / hidden (native) | Delegated image picking; return a URL/URI or null |
| onPickDate | ({ key, label, current }) => Promise<string \| null> | text input | Native only — delegated date picking |
| defaultWallet | "apple" \| "google" | "apple" | Which preview shows first |
| barcodeRenderer | ({ format, message }) => ReactNode | illustrative | Plug a real QR (see below) |
| hideExport | boolean | false | Hide the built-in export panel |
| className / style | — | — | Applied to the root (web) / root View (native) |
<PassPreview>
| Prop | Type | Default |
| --- | --- | --- |
| pass | WalletPass | — |
| wallet | "apple" \| "google" | "apple" |
| barcodeRenderer | ({ format, message }) => ReactNode | illustrative |
| className / style | — | — |
Pass types
Set kind on the design (or let the user pick). Each maps to an Apple style and a Google pass type when signed:
| kind | Apple style | Google type | Kind-specific fields (✅ = required to sign) |
| --- | --- | --- | --- |
| eventTicket (default) | eventTicket | Event ticket | eventName ✅, venueName, date, doorsOpen, holderName, seat, ticketType |
| boardingPass | boardingPass | Transit | transitType ✅, origin ✅, destination ✅, passengerName, boardingTime, gate, seat, carrierName, tripNumber |
| coupon | coupon | Offer | offerName ✅, provider, terms, promoCode |
| storeCard | storeCard | Loyalty | programName ✅, memberName, memberId, balance, tier |
| generic | generic | Generic | title, subtitle, backFields |
All kinds share branding (colors, logoText, images), a barcode, expiry, unlimited back-of-pass detail rows, and the Google hosted-URL branding (logoUrl, heroImageUrl, homepageUrl).
Google requires a hosted
logoUrlforboardingPassandstoreCard. The designer surfaces this as an inline warning until the field is filled — matching the validation inpass-generator.
The exported design
onExport / the export buttons produce a cleaned WalletPass (also available programmatically via buildExport(pass)):
{
"kind": "eventTicket",
"serialNumber": "PASS-0001",
"description": "Concert ticket",
"organizationName": "Acme Tickets",
"eventName": "Summer Synth Festival",
"date": "2026-07-18T17:00:00.000Z",
"colors": { "background": "#1c1c28", "foreground": "#ffffff", "label": "#b6b6c8" },
"barcode": { "format": "qr", "message": "PASS-0001|verify-token" },
"images": { "icon": "icon.png", "logo": "logo.png" },
"backFields": [{ "label": "Terms", "value": "Non-refundable." }]
}Notes for the developer receiving it:
- Images export as placeholder filenames (
icon.png,logo.png, …) — uploaded previews are for layout only. Supply real files/URLs when signing. - Dates are ISO strings — convert to
Dateobjects forpass-generator. - The "Copy developer snippet" button produces a ready-to-paste signing snippet.
From design to a real pass
Signing needs private certificates, so it always happens on a server — never in the client where this package runs:
Designer (this package, client) Your server (pass-generator) User's phone
design JSON ───────────────────▶ createApplePass(design, …) ───────▶ .pkpass → Apple Wallet
createGoogleWalletSaveUrl(…) ──────▶ Save URL → Google Wallet// server-side
import { createApplePass, createGoogleWalletSaveUrl } from "@nikpnevmatikos/pass-generator";
const pkpass = await createApplePass(design, { identity, certificates }); // .pkpass Buffer
const saveUrl = createGoogleWalletSaveUrl(design, { credentials }); // Google Save URLSee the pass-generator repo for certificate setup and a runnable delivery-server example.
Real barcodes
The built-in barcode is illustrative (deterministic, not scannable) so the package stays dependency-free. Render a real one with barcodeRenderer:
import { QRCodeSVG } from "qrcode.react"; // web — or rn-qr-generator etc. on native
<PassPreview
pass={pass}
barcodeRenderer={({ message, format }) =>
format === "qr" ? <QRCodeSVG value={message} size={116} /> : null
}
/>Theming (web)
Override the CSS variables on the root (or any ancestor):
.pdx { --pd-accent: #6D28D9; --pd-canvas: #F5F5F7; }On native, pass a style to the root; the exported palette object documents the default tokens.
Examples
| Folder | What | Run |
| --- | --- | --- |
| example/ | Vite + React web demo with a props playground | npm install && npm run dev |
| example-expo/ | Expo (SDK 54) React Native demo | npm install && npx expo start |
Both consume the built package — run npm run build at the repo root after changing src/.
Development
npm install
npm run typecheck # web + native configs
npm run build # tsup → dist (web ESM/CJS + native ESM/CJS + d.ts)License
MIT © Nik Pnevmatikos
