A React component library for rendering common AT Protocol records for applications such as Bluesky and Leaflet.
1# atproto-ui 2 3A React component library for rendering AT Protocol records (Bluesky, Leaflet, Tangled, and more). Handles DID resolution, PDS discovery, and record fetching automatically. [Live demo](https://atproto-ui.wisp.place). 4 5## Screenshots 6 7![Bluesky component](readme_img/bluesky.png) 8![Tangled String component](readme_img/tangled.png) 9 10## Features 11 12- **Drop-in components** for common record types (`BlueskyPost`, `BlueskyProfile`, `TangledString`, `LeafletDocument`) 13- **Prefetch support** - Pass data directly to skip API calls (perfect for SSR/caching) 14- **Customizable theming** - Override CSS variables to match your app's design 15- **Composable hooks** - Build custom renderers with protocol primitives 16- Built on lightweight [`@atcute/*`](https://tangled.org/@mary.my.id/atcute) clients 17 18## Installation 19 20```bash 21npm install atproto-ui 22``` 23 24## Quick Start 25 26```tsx 27import { AtProtoProvider, BlueskyPost, LeafletDocument } from "atproto-ui"; 28 29export function App() { 30 return ( 31 <AtProtoProvider> 32 <BlueskyPost did="did:plc:example" rkey="3k2aexample" /> 33 {/* You can use handles too */} 34 <LeafletDocument did="nekomimi.pet" rkey="3m2seagm2222c" /> 35 </AtProtoProvider> 36 ); 37} 38``` 39 40**Note:** The library automatically imports the CSS when you import any component. If you prefer to import it explicitly (e.g., for better IDE support or control over load order), you can use `import "atproto-ui/styles.css"`. 41 42## Theming 43 44Components use CSS variables for theming. By default, they respond to system dark mode preferences, or you can set a theme explicitly: 45 46```tsx 47// Set theme via data attribute on document element 48document.documentElement.setAttribute("data-theme", "dark"); // or "light" 49 50// For system preference (default) 51document.documentElement.removeAttribute("data-theme"); 52``` 53 54### Available CSS Variables 55 56```css 57--atproto-color-bg 58--atproto-color-bg-elevated 59--atproto-color-text 60--atproto-color-text-secondary 61--atproto-color-border 62--atproto-color-link 63/* ...and more */ 64``` 65 66### Override Component Theme 67 68Wrap any component in a div with custom CSS variables to override its appearance: 69 70```tsx 71<div style={{ 72 '--atproto-color-bg': '#f0f0f0', 73 '--atproto-color-text': '#000', 74 '--atproto-color-link': '#0066cc', 75}}> 76 <BlueskyPost did="..." rkey="..." /> 77</div> 78``` 79 80## Prefetched Data 81 82All components accept a `record` prop. When provided, the component uses your data immediately without making network requests. Perfect for SSR, caching, or when you've already fetched data. 83 84```tsx 85import { BlueskyPost, useLatestRecord } from "atproto-ui"; 86import type { FeedPostRecord } from "atproto-ui"; 87 88const MyComponent: React.FC<{ did: string }> = ({ did }) => { 89 // Fetch the latest post using the hook 90 const { record, rkey, loading } = useLatestRecord<FeedPostRecord>( 91 did, 92 "app.bsky.feed.post" 93 ); 94 95 if (loading) return <p>Loading</p>; 96 if (!record || !rkey) return <p>No posts found.</p>; 97 98 // Pass the fetched record directly—BlueskyPost won't re-fetch it 99 return <BlueskyPost did={did} rkey={rkey} record={record} />; 100}; 101``` 102 103All components support prefetched data: 104 105```tsx 106<BlueskyProfile did={did} record={profileRecord} /> 107<TangledString did={did} rkey={rkey} record={stringRecord} /> 108<LeafletDocument did={did} rkey={rkey} record={documentRecord} /> 109``` 110 111## API Reference 112 113### Components 114 115| Component | Description | 116|-----------|-------------| 117| `AtProtoProvider` | Context provider for sharing protocol clients. Optional `plcDirectory` prop. | 118| `AtProtoRecord` | Core component for fetching/rendering any AT Protocol record. Accepts `record` prop. | 119| `BlueskyProfile` | Profile card for a DID/handle. Accepts `record`, `fallback`, `loadingIndicator`, `renderer`. | 120| `BlueskyPost` | Single Bluesky post. Accepts `record`, `iconPlacement`, custom renderers. | 121| `BlueskyQuotePost` | Post with quoted post support. Accepts `record`. | 122| `BlueskyPostList` | Paginated list of posts (default: 5 per page). | 123| `TangledString` | Tangled string (code snippet) renderer. Accepts `record`. | 124| `LeafletDocument` | Long-form document with blocks. Accepts `record`, `publicationRecord`. | 125 126### Hooks 127 128| Hook | Returns | 129|------|---------| 130| `useDidResolution(did)` | `{ did, handle, loading, error }` | 131| `useLatestRecord(did, collection)` | `{ record, rkey, loading, error, empty }` | 132| `usePaginatedRecords(options)` | `{ records, loading, hasNext, loadNext, ... }` | 133| `useBlob(did, cid)` | `{ url, loading, error }` | 134| `useAtProtoRecord(did, collection, rkey)` | `{ record, loading, error }` | 135 136## Advanced Usage 137 138### Using Hooks for Custom Logic 139 140```tsx 141import { useLatestRecord, BlueskyPost } from "atproto-ui"; 142import type { FeedPostRecord } from "atproto-ui"; 143 144const LatestBlueskyPost: React.FC<{ did: string }> = ({ did }) => { 145 const { record, rkey, loading, error, empty } = useLatestRecord<FeedPostRecord>( 146 did, 147 "app.bsky.feed.post", 148 ); 149 150 if (loading) return <p>Fetching latest post</p>; 151 if (error) return <p>Could not load: {error.message}</p>; 152 if (empty || !record || !rkey) return <p>No posts yet.</p>; 153 154 // Pass both record and rkey—no additional API call needed 155 return <BlueskyPost did={did} rkey={rkey} record={record} colorScheme="system" />; 156}; 157``` 158 159### Custom Renderer 160 161Use `AtProtoRecord` with a custom renderer for full control: 162 163```tsx 164import { AtProtoRecord } from "atproto-ui"; 165import type { FeedPostRecord } from "atproto-ui"; 166 167<AtProtoRecord<FeedPostRecord> 168 did={did} 169 collection="app.bsky.feed.post" 170 rkey={rkey} 171 renderer={({ record, loading, error }) => ( 172 <article> 173 <strong>{record?.text ?? "Empty post"}</strong> 174 </article> 175 )} 176/> 177``` 178 179## Demo 180 181Check out the [live demo](https://atproto-ui.wisp.place/) to see all components in action. 182 183### Running Locally 184 185```bash 186npm install 187npm run dev 188``` 189 190## Contributing 191 192Contributions are welcome! Open an issue or PR for: 193- New record type support (e.g., Grain.social photos) 194- Improved documentation 195- Bug fixes or feature requests 196 197## License 198 199MIT