content surface · pds + constellation

<atproto-profile>

Profile card with banner, avatar, bio, follower / following / post counts.

<atproto-profile src="pfrazee.com"></atproto-profile>
<atproto-profile src="did:plc:..."></atproto-profile>
<atproto-profile src="at://did:plc:..."></atproto-profile>
NameTypeDefaultDescription
src * string Handle, DID, or AT-URI.
constellation string Override the Constellation endpoint.

External CSS can target these via atproto-profile::part(<name>) { ... }.

PartWhat it is
articleOuter card.
banner / banner-imageBanner strip and inner img.
headAvatar + names cluster.
avatar / avatar-imageAvatar circle and inner img.
names / display-name / handleName + handle stack.
descriptionProfile bio paragraph.
stats / statFollowers / following / posts row.

Renders a full profile card — banner, avatar, display name, handle, bio, and three count stats. Content comes from the subject's own PDS (their app.bsky.actor.profile/self record); the follower count comes from Constellation; following and post counts are derived by paginating listRecords on the subject's own repo, capped at 500.

There's no cheap count endpoint for a repo's own records. The component paginates listRecords up to 500 items and caps display there — real numbers above that show as 500+. That's a deliberate tradeoff: an accurate count on a 10,000-follow account would cost 100 sequential PDS calls, and the exact number rarely matters to readers. If you need more, override the cap in source (countOwnRecords in atproto-profile.ts) — it's a one-line change.

  • <atproto-profile> — full card. Default choice.
  • Compose atomics (<atproto-avatar> + <atproto-display-name> + <atproto-handle>) — when you want a smaller inline presence (author line in a comment, sidebar byline) without the banner/bio/stats chrome.
  • <atproto-lexicon-viewer src="at://did/app.bsky.actor.profile/self"> — when you want to see the raw profile record (debugging).

All color / typography / radius tokens apply. Parts let you target specific pieces — a common tweak is squaring the avatar or hiding stats:

atproto-profile::part(avatar) { border-radius: 4px; }
atproto-profile::part(stats) { display: none; }
atproto-profile::part(banner) { height: 200px; }