API reference
@scribe-atp/social
Components
LikeButton
function LikeButton(props: LikeButtonProps): JSX.Element
A React button that creates an AT Protocol site.standard.graph.recommend record ("recommend") for an article when clicked. Opens a popup OAuth flow at social.scribe-atp.app, then updates to a confirmed state on success.
LikeButtonProps
| Prop | Type | Required | Description |
|---|---|---|---|
documentUri | string | ✓ | AT URI of the site.standard.document record |
publicationUri | string | ✓ | AT URI of the site.standard.publication record |
title | string | ✓ | Article title — shown in the like popup |
serviceUrl | string | — | Override the social service base URL. Defaults to "https://social.scribe-atp.app" |
className | string | — | Additional CSS class appended to the base scribe-atp-like-button class |
children | ReactNode | ((isLiked: boolean) => ReactNode) | — | Custom button label. Omit to use the defaults "Like" / "Liked ✓" |
onSuccess | () => void | — | Called after a successful like |
defaultLiked | boolean | — | Initial liked state. Pass true from an SSR loader to avoid a flash of unconfirmed state on first render |
import { LikeButton } from '@scribe-atp/social';
<LikeButton
documentUri="at://did:plc:abc123/site.standard.document/3jxtctq7kqm2y"
publicationUri="at://did:plc:abc123/site.standard.publication/3mp4nd46xwr2h"
title="My Article Title"
/>
Getting the URIs: Use fetchArticleBySlug from @scribe-atp/core — it returns { article, uri } where uri is the document AT URI. fetchSite returns a Site object whose uri is the publication AT URI. If you're using @scribe-atp/react-router-framework, createArticleRouteLoader includes documentUri in the loader return.
Liked state persistence: Once liked, the result is stored in localStorage under the key scribe:recommended:{documentUri}. The button renders in its confirmed state on future visits without another network call.
Accessibility: Uses aria-pressed to communicate liked state. The button remains in the tab order after being activated — it is never disabled.
ShareButton
function ShareButton(props: ShareButtonProps): JSX.Element
A React button that opens a popup for the reader to share the article via their Bluesky account. After a successful share, the button briefly enters a confirmed state then resets, allowing the reader to share again.
ShareButtonProps
| Prop | Type | Required | Description |
|---|---|---|---|
documentUri | string | ✓ | AT URI of the site.standard.document record |
publicationUri | string | ✓ | AT URI of the site.standard.publication record |
title | string | ✓ | Article title — shown in the share popup |
canonicalUrl | string | — | Canonical URL of the article. Defaults to window.location.href |
serviceUrl | string | — | Override the social service base URL. Defaults to "https://social.scribe-atp.app" |
className | string | — | Additional CSS class appended to the base scribe-atp-share-button class |
children | ReactNode | ((isShared: boolean) => ReactNode) | — | Custom button label. Omit to use the defaults "Share" / "Shared ✓" |
onSuccess | () => void | — | Called after a successful share |
import { ShareButton } from '@scribe-atp/social';
<ShareButton
documentUri="at://did:plc:abc123/site.standard.document/3jxtctq7kqm2y"
publicationUri="at://did:plc:abc123/site.standard.publication/3mp4nd46xwr2h"
title="My Article Title"
/>
Note: Unlike LikeButton and SubscribeButton, the shared state is not persisted to localStorage. The confirmed state resets after 3 seconds so the reader can share again.
SubscribeButton
function SubscribeButton(props: SubscribeButtonProps): JSX.Element
A React button that follows an author's publication on the AT Protocol when clicked. Opens a popup OAuth flow, then updates to a confirmed state on success. When the reader is already subscribed, clicking the button opens an unsubscribe confirmation popup — the button acts as a toggle.
SubscribeButtonProps
| Prop | Type | Required | Description |
|---|---|---|---|
publicationUri | string | ✓ | AT URI of the site.standard.publication record |
title | string | ✓ | Publication name — shown in the subscribe/unsubscribe popup |
serviceUrl | string | — | Override the social service base URL. Defaults to "https://social.scribe-atp.app" |
className | string | — | Additional CSS class appended to the base scribe-atp-subscribe-button class |
children | ReactNode | ((isSubscribed: boolean) => ReactNode) | — | Custom button label. Omit to use the defaults "Subscribe" / "Subscribed ✓". The render prop receives isSubscribed so you can show different copy in each state |
onSuccess | () => void | — | Called after a successful subscription |
onUnsubscribe | () => void | — | Called after a confirmed unsubscription |
defaultSubscribed | boolean | — | Initial subscribed state. Pass true from an SSR loader to avoid a flash of unconfirmed state on first render |
import { SubscribeButton } from '@scribe-atp/social';
<SubscribeButton
publicationUri="at://did:plc:abc123/site.standard.publication/3mp4nd46xwr2h"
title="My Site"
/>
Getting the publicationUri: fetchSite from @scribe-atp/core returns a Site object whose uri field is the publication AT URI. createArticleRouteLoader also returns publicationUri in the loader data.
Subscribed state persistence: Stored in localStorage under scribe:subscribed:{publicationUri}. Cleared automatically after a confirmed unsubscription.
Accessibility: Uses aria-pressed to communicate subscribed state. The button remains in the tab order after being activated — it is never disabled.
Storage utilities
Exported for reading or setting liked/subscribed state outside the components — e.g. for pre-checking state server-side or in tests.
isRecommended
function isRecommended(documentUri: string): boolean
Returns true if the current user has already liked the given document.
markRecommended
function markRecommended(documentUri: string): void
Persists a liked state to localStorage. Useful if you've handled the OAuth flow yourself and want the button to reflect it.
isSubscribed
function isSubscribed(publicationUri: string): boolean
Returns true if the current user has already subscribed to the given publication.
markSubscribed
function markSubscribed(publicationUri: string): void
Persists a subscribed state to localStorage.
All four storage functions above are SSR-safe — localStorage access is wrapped in a try/catch. They return false / do nothing when called in environments where localStorage is unavailable.
Note
A fifth function, clearSubscribed, exists internally and is called automatically by SubscribeButton after a confirmed unsubscription — but it isn't exported from the package's public API. If you're building your own unsubscribe UI outside SubscribeButton, note that markSubscribed won't help here (it only sets the subscribed state, not clears it) — you'll need to track unsubscribed state with your own localStorage key for now.
How the popup flow works
- The user clicks a button. A
480×640popup window opens atsocial.scribe-atp.appwith the relevant AT URI, atitle, and a one-timetokenas query params. - The user authenticates with their Bluesky account via AT Protocol OAuth.
- On success, the service writes the like, share, or follow record and notifies the opener page. Notification uses
postMessageifwindow.openeris available, or the button polls/status/:tokenas a fallback (handles browsers that block cross-originpostMessageafter popup close). - The button transitions to its confirmed state. For
LikeButtonandSubscribeButtonthe state is persisted tolocalStorage; forShareButtonthe confirmed state resets after 3 seconds.
The polling fallback runs for up to 30 seconds (20 attempts at 1.5 s intervals) before stopping.
See the Social interactions guide for full integration examples.