Skip to content

@scribe-atp/social

function LikeButton(props: LikeButtonProps): JSX.Element

A 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

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.


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.


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.


Exported for reading or setting liked/subscribed state outside the components — e.g. for pre-checking state server-side or in tests.

function isRecommended(documentUri: string): boolean

Returns true if the current user has already liked the given document.


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.


function isSubscribed(publicationUri: string): boolean

Returns true if the current user has already subscribed to the given publication.


function markSubscribed(publicationUri: string): void

Persists a subscribed state to localStorage.


function clearSubscribed(publicationUri: string): void

Removes 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.


  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.