A nostr client built as independent, swappable parts. Vanilla JavaScript, no build step, no bundler, no framework. One repo, one thing — eleven clients, a built-in bitcoin wallet, and a theme system, all from the same components. Everything served from GitHub Pages. AGPL.
Composed clients are just pages that arrange parts — so they come in grades (new here? join nostr in 60 seconds):
| grade | client | character |
|---|---|---|
| minimal | zen | read only — serif, moss, world & circle, conversations. Nothing else. |
| long-form | reader | a quiet magazine for NIP-23 articles |
| daily | micro | post, reply, react, follow, mentions, search, profile, relays |
| full | monty | the full monty — relay explorer, in-app profiles, hashtags, on-chain tips |
| replica | skyline | bluesky-flavored — same parts wearing app.bsky's outfit 🦋 |
| replica | birdsite | twitter/X-flavored — For you, trends, the blue 𝕏 |
| replica | dittly | mastodon/ditto-flavored — columns and the elephant 🐘 |
| replica | bookface | facebook-flavored — friends, cards, what's on your mind |
| replica | jumbly | jumble-flavored — browse relays like channels 🪩 |
| wallet | purse | the little bitcoin wallet that lives in your nostr key 👛 |
| replica | btcnostr | bchnostr-flavored, for bitcoin — built-in testnet wallet, on-chain tips ₿ |
Same components, different HTML files. zen's entire "fork" is two CSS custom properties.
view-source: is the docs.npub…/nsec… are presentation only
(nip19), and a key is also a W3C identifier:
did:nostr.| repo | one thing | demo |
|---|---|---|
| pool | relay pool — subscribe/list/get/publish, dedup, reconnect | live feed |
| nip19 | display encoding — npub/note at the UI edge only | converter |
| login | <nostr-login> — NIP-07 / guest keys, NIP-98 fetch | login + NIP-98 |
| did | did:nostr identity documents, 3-tier resolution | resolver |
| did-nostr | whois for nostr — DID + profile + bitcoin address + walkable graph | explorer |
| verify | event id + schnorr verification; wraps any pool | forgery test |
| theme | design tokens + ten named themes — reskin every client | theme gallery |
| feed | <nostr-feed> — live notes, batched profile resolution | global feed |
| note | <nostr-note> — one note card + shared safe rendering | note |
| thread | <nostr-thread> — NIP-10 conversations, live reply tree | thread |
| composer | <nostr-composer> — publish kind 1 with per-relay acks | composer |
| reactions | <nostr-reactions> — likes & reposts, lazy counts | reactions |
| contacts | follow button + kind-3 list editor, preserves metadata | contacts |
| notifications | <nostr-notifications> — mentions/replies/reactions to you | notifications |
| profile | <nostr-profile> — profile card for any pubkey | profile page |
| profile-editor | <nostr-profile-editor> — edit kind 0, preserves unknown fields | editor |
| relay-manager | <nostr-relay-manager> — page relays + NIP-65 lists | relays |
| search | <nostr-search> — NIP-50 full-text search | search |
| nip46 | remote signing via nsec bunker (experimental) | bunker |
| cache | IndexedDB event cache — wraps any pool | instant reload |
| guestbook | comments for any website, stored on nostr (NIP-22) | comment |
| groups | mailing lists on nostr — NIP-72 communities, gmail-style reader | groups |
| chat | IRC-style rooms on nostr (NIP-28), live | chat |
| chess | real-time chess — the moves are signed events | play |
| ameba | digital organisms — typed bot messaging, encrypted channels, host-independent memory 🦠 | petri dish |
| wallet | in-browser bitcoin wallet + instant tip button (testnet) | wallet |
| tip | <nostr-tip> — on-chain bitcoin tips, testnet-first | ₿ tip |
| article | <nostr-article> — NIP-23 long-form, safe markdown | article |
| embed | put nostr on any website — copy-paste snippets | generator |
| pulse | the live heartbeat of nostr — no backend | pulse |
| reader | composed client, long-form grade — a quiet magazine | reader |
| zen | composed client, minimal grade — read only | zen |
| micro | composed client, daily grade — the essentials | micro |
| monty | composed client, full grade — everything | monty |
| skyline | composed client, replica grade — bluesky-flavored | skyline |
| starter | join nostr in 60 seconds — key, profile, follows, hello | start |
A working nostr page, from nothing, no toolchain:
<script type="module" src="https://nostr-client.github.io/login/login.js"></script>
<script type="module" src="https://nostr-client.github.io/feed/feed.js"></script>
<script type="module" src="https://nostr-client.github.io/composer/composer.js"></script>
<nostr-login></nostr-login>
<nostr-composer></nostr-composer>
<nostr-feed limit="20"></nostr-feed>
Parts interoperate through five small conventions — see CONTRACT.md:
1. hex is the primitive 64-char hex pubkeys/ids everywhere; NIP-19 is display-only
2. the signer window.nostrSigner = { type, getPublicKey(), signEvent(evt) }
announced via 'nostr:login' / 'nostr:logout' on window
3. the pool defaultPool() — one websocket per relay per page,
however many components you compose
4. events components emit bubbling 'nostr:*' CustomEvents
5. swapping absolute-URL ES module imports — override any of them
with an import map, or inject .pool / .signer properties
This org itself has a nostr identity: agent.did.json — a did:nostr document, because a nostr public key is already a W3C-resolvable identifier.