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

PropTypeRequiredDescription
documentUristringAT URI of the site.standard.document record
publicationUristringAT URI of the site.standard.publication record
titlestringArticle title — shown in the like popup
serviceUrlstringOverride the social service base URL. Defaults to "https://social.scribe-atp.app"
classNamestringAdditional CSS class appended to the base scribe-atp-like-button class
childrenReactNode | ((isLiked: boolean) => ReactNode)Custom button label. Omit to use the defaults "Like" / "Liked ✓"
onSuccess() => voidCalled after a successful like
defaultLikedbooleanInitial 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

PropTypeRequiredDescription
documentUristringAT URI of the site.standard.document record
publicationUristringAT URI of the site.standard.publication record
titlestringArticle title — shown in the share popup
canonicalUrlstringCanonical URL of the article. Defaults to window.location.href
serviceUrlstringOverride the social service base URL. Defaults to "https://social.scribe-atp.app"
classNamestringAdditional CSS class appended to the base scribe-atp-share-button class
childrenReactNode | ((isShared: boolean) => ReactNode)Custom button label. Omit to use the defaults "Share" / "Shared ✓"
onSuccess() => voidCalled 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

PropTypeRequiredDescription
publicationUristringAT URI of the site.standard.publication record
titlestringPublication name — shown in the subscribe/unsubscribe popup
serviceUrlstringOverride the social service base URL. Defaults to "https://social.scribe-atp.app"
classNamestringAdditional CSS class appended to the base scribe-atp-subscribe-button class
childrenReactNode | ((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() => voidCalled after a successful subscription
onUnsubscribe() => voidCalled after a confirmed unsubscription
defaultSubscribedbooleanInitial 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

  1. The user clicks a button. A 480×640 popup window opens at social.scribe-atp.app with the relevant AT URI, a title, and a one-time token as query params.
  2. The user authenticates with their Bluesky account via AT Protocol OAuth.
  3. On success, the service writes the like, share, or follow record and notifies the opener page. Notification uses postMessage if window.opener is available, or the button polls /status/:token as a fallback (handles browsers that block cross-origin postMessage after popup close).
  4. The button transitions to its confirmed state. For LikeButton and SubscribeButton the state is persisted to localStorage; for ShareButton the 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.

Settings

Appearance