Client-side comments, likes & reposts from a linked Bluesky post. No auth, no server, no dependencies.
This widget fetches engagement data from the public Bluesky API and renders it inline. It works as a progressive enhancement — if the API is unreachable or the post doesn't exist, the widget simply doesn't render.
Enter any Bluesky post URL to see its engagement:
Add a container element with data-bsky-uri and include the script:
<!-- Container: set data-bsky-uri to your Bluesky post -->
<div data-bsky-uri="https://bsky.app/profile/you.bsky.social/post/abc123"></div>
<!-- Widget script (defer recommended) -->
<script type="module" src="/bsky/bsky-engagement.js"></script>
All options are set via data- attributes:
<div data-bsky-uri="https://bsky.app/profile/..."
data-bsky-sort="likes" <!-- likes | newest | oldest -->
data-bsky-max-depth="3" <!-- reply nesting depth -->
data-bsky-show="all" <!-- all | comments | stats -->
data-bsky-theme="auto" <!-- auto | light | dark -->
></div>
The container emits custom events you can listen to:
const el = document.querySelector('[data-bsky-uri]');
el.addEventListener('bsky:loaded', (e) => {
console.log('Post:', e.detail.post);
console.log('Replies:', e.detail.replies.length);
console.log('Likes:', e.detail.likes.length);
});
el.addEventListener('bsky:error', (e) => {
console.warn('Widget failed:', e.detail.error);
});
Override any CSS custom property on .bsky-engagement:
.bsky-engagement {
--bsky-font: 'Your Font', sans-serif;
--bsky-text: #333;
--bsky-accent: #0085ff;
--bsky-heart: #ec4899;
--bsky-repost: #22c55e;
--bsky-border: #eee;
--bsky-radius: 8px;
}