@scribe-atp/social
Components
Section titled “Components”LikeButton
Section titled “LikeButton”function LikeButton(props: LikeButtonProps): JSX.ElementA React button that creates an AT Protocol app.bsky.feed.like 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
Section titled “ShareButton”function ShareButton(props: ShareButtonProps): JSX.ElementA 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
Section titled “SubscribeButton”function SubscribeButton(props: SubscribeButtonProps): JSX.ElementA 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
Section titled “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
Section titled “isRecommended”function isRecommended(documentUri: string): booleanReturns true if the current user has already liked the given document.
markRecommended
Section titled “markRecommended”function markRecommended(documentUri: string): voidPersists a liked state to localStorage. Useful if you’ve handled the OAuth flow yourself and want the button to reflect it.
isSubscribed
Section titled “isSubscribed”function isSubscribed(publicationUri: string): booleanReturns true if the current user has already subscribed to the given publication.
markSubscribed
Section titled “markSubscribed”function markSubscribed(publicationUri: string): voidPersists a subscribed state to localStorage.
clearSubscribed
Section titled “clearSubscribed”function clearSubscribed(publicationUri: string): voidRemoves the subscribed state from localStorage for the given publication. Called automatically after a confirmed unsubscription via SubscribeButton.
All five storage functions are SSR-safe — localStorage access is wrapped in a try/catch. They return false / do nothing when called in environments where localStorage is unavailable.
How the popup flow works
Section titled “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.