Embed Bluesky Comment Threads Anywhere With bsky-comments

Embed Bluesky Comment Threads Anywhere With bsky-comments
bsky-comments is a Web Component that embeds Bluesky discussion threads directly on any webpage.

It calls the AT Protocol’s public HTTP API with native fetch to retrieve thread data and renders the output as semantic HTML in the Light DOM.

The component registers as the <bsky-comments> custom element and works in plain HTML files and in framework projects including React, Vue, Svelte, and Astro.

Features

  • Roughly 3 kB gzipped total size with no external SDK bundled.
  • Accepts a standard public Bluesky post URL or a raw AT-URI as the source.
  • Renders all output in the Light DOM for direct CSS control.
  • Ascending and descending sort order.
  • Rich text support for mentions, hashtags, and inline links using AT Protocol facet data.
  • Customizable like and reply icons via HTML attributes or CSS pseudo-elements.
  • Configurable PDS endpoint for self-hosted Bluesky instances.
  • Lazy-loaded avatar images to reduce initial page load impact.
  • Recursive rendering of nested reply threads up to a configurable depth.
  • Cancels in-flight fetch requests on element disconnect to prevent stale state overwrites.

How to use it:

1. Install & download.

# NPM
$ npm install bsky-comments

2. Load the component from a CDN:

<script type="module" src="https://unpkg.com/bsky-comments"></script>

3. Add the <bsky-comments> component to your webpage and paste the URL directly from your browser. The component resolves the handle to a DID automatically.

<!-- The post attribute accepts any standard bsky.app post URL -->
<bsky-comments
  post="https://bsky.app/profile/yourhandle.bsky.social/post/abc123xyz456"
></bsky-comments>

4. When your build process already knows the DID, pass the AT-URI via the `uri` attribute. This skips the handle-resolution API call entirely.

<!-- uri takes precedence over post when both are set -->
<!-- Use this on static sites where the DID is known at build time -->
<bsky-comments
  uri="at://did:plc:exampleDID12345/app.bsky.feed.post/abc123xyz456"
></bsky-comments>

5. Show newest comments first:

<bsky-comments
  post="https://bsky.app/profile/yourhandle.bsky.social/post/abc123xyz456"
  sort="desc"
></bsky-comments>

6. Pass raw emoji or inline SVG directly into the icon attributes:

<!-- Replace the default heart and chat-bubble icons with emoji -->
<bsky-comments
  post="https://bsky.app/profile/yourhandle.bsky.social/post/abc123xyz456"
  icon-like="💙"
  icon-reply="↩️"
></bsky-comments>

<!-- Or use inline SVG for pixel-perfect control -->
<bsky-comments
  post="https://bsky.app/profile/yourhandle.bsky.social/post/abc123xyz456"
  icon-like='<svg viewBox="0 0 24 24" width="16" height="16" fill="currentColor"><path d="M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5A5.5 5.5 0 0 1 7.5 3c1.74 0 3.41.81 4.5 2.09A6.01 6.01 0 0 1 16.5 3 5.5 5.5 0 0 1 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z"/></svg>'
></bsky-comments>
/* Hide the default emoji and replace it with a background image */
.bsky-icon-like {
  display: inline-block;
  width: 16px;
  height: 16px;
  color: transparent; /* Makes the default emoji invisible */
  background-image: url('/icons/heart.svg');
  background-size: contain;
  background-repeat: no-repeat;
}

7. All available props for the <bsky-comments> component.

  • post (string): The public web URL of the Bluesky post (e.g., https://bsky.app/profile/...). The component resolves the handle to a DID automatically via one extra API call.
  • uri (string): The internal AT-URI (e.g., at://did:plc:...). When set, this takes precedence over post and skips handle resolution.
  • sort (string): Comment sort order. Accepts asc (oldest first, default) or desc (newest first).
  • service (string): The PDS API base URL. Defaults to public.api.bsky.app. Set this for self-hosted Bluesky instances.
  • icon-like (string): Custom HTML string or emoji for the like icon. Defaults to ❤️.
  • icon-reply (string): Custom HTML string or emoji for the reply icon. Defaults to 💬.
  • depth (number): Maximum nesting depth for fetched replies. Defaults to 10.

8. Customize the component with the following CSS classes:

/* Add a left border and bottom spacing to each comment */
.bsky-comment {
  border-left: 2px solid #e5e7eb;
  padding-left: 1rem;
  margin-bottom: 1.25rem;
}

/* Style the actions row for likes and replies */
.bsky-actions {
  display: flex;
  gap: 1rem;
  font-size: 0.875rem;
  color: #6b7280;
  margin-top: 0.5rem;
}

/* Indent nested reply threads */
.bsky-replies {
  margin-top: 0.75rem;
  padding-left: 1rem;
}

9. Or via TailwindCSS:

<!-- Wrap the component and target its internal classes with Tailwind selectors -->
<div class="
  [&_.bsky-comment]:border-l-2
  [&_.bsky-comment]:border-gray-200
  [&_.bsky-comment]:pl-4
  [&_.bsky-comment]:mb-4
  [&_.bsky-actions]:text-sm
  [&_.bsky-actions]:text-gray-500
  [&_.bsky-actions]:flex
  [&_.bsky-actions]:gap-4
">
  <bsky-comments post="https://bsky.app/profile/yourhandle.bsky.social/post/abc123xyz456" />
</div>

Framework Integration

 

React / Next.js:

 

// Import once to register the custom element globally
import 'bsky-comments';

export function PostPage() {
  return (
    <section className="comments-section">
      {/* Use it like any other HTML element in JSX */}
      <bsky-comments post="https://bsky.app/profile/yourhandle.bsky.social/post/abc123xyz456" />
    </section>
  );
}

Framework Integration:

Vue / Nuxt:

<script setup>
// Registering the custom element on component mount
import 'bsky-comments';
</script>

<template>
  <!-- Bind the post URL from your reactive state -->
  <bsky-comments :post="currentPostUrl" />
</template>

Svelte / SvelteKit:

<script>
  // Import at the top of your layout or page file
  import 'bsky-comments';
</script>

<!-- The component renders as a standard HTML element -->
<bsky-comments post="https://bsky.app/profile/yourhandle.bsky.social/post/abc123xyz456" />

Astro:

---
// Import inside the frontmatter block for server-side bundling
import 'bsky-comments';
---

<!-- Pass a static post URL resolved at build time -->
<bsky-comments post="https://bsky.app/profile/yourhandle.bsky.social/post/abc123xyz456" />

Alternatively, use the CDN import in an Astro <script> tag:

---
---

<bsky-comments post="https://bsky.app/profile/yourhandle.bsky.social/post/abc123xyz456" />

<script>
  // Loads the component at runtime on the client side
  import 'bsky-comments';
</script>

FAQs:

Q: What is the difference between the post and uri attributes?
A: post accepts a standard https://bsky.app/... URL and resolves the handle to a DID via one extra API call. uri accepts a pre-resolved at://did:plc:... string and skips that step. Use uri on static sites where the DID is available at build time to cut the request count in half.

Q: Why am I seeing a “Could not resolve handle” error?
A: The handle in the post URL no longer resolves on the public Bluesky network, usually because the account was renamed or deleted. Pass the uri attribute with the full AT-URI to bypass handle resolution.

Q: Does Tailwind CSS work with this component?
A: Yes. The component renders in the Light DOM, so Tailwind’s arbitrary variant selectors (e.g., [&_.bsky-comment]:border-l-2) apply directly on the wrapper element.

Q: Can I point the component at a self-hosted Bluesky instance?
A: Yes. Set the service attribute to your PDS base URL (e.g., service="https://pds.example.com/xrpc"). The component sends all API requests to that endpoint.

The post Embed Bluesky Comment Threads Anywhere With bsky-comments appeared first on CSS Script.


Discover more from RSS Feeds Cloud

Subscribe to get the latest posts sent to your email.

Embed Bluesky Comment Threads Anywhere With bsky-comments

Embed Bluesky Comment Threads Anywhere With bsky-comments
Embed Bluesky Comment Threads Anywhere With bsky-comments
bsky-comments is a Web Component that embeds Bluesky discussion threads directly on any webpage.It calls the AT Protocol’s public HTTP API with native fetch to retrieve thread data and renders the output as semantic HTML in the Light DOM.The component registers as the <bsky-comments> custom element and works in plain HTML files and in framework projects including React, Vue, Svelte, and Astro.

Features

  • Roughly 3 kB gzipped total size with no external SDK bundled.
  • Accepts a standard public Bluesky post URL or a raw AT-URI as the source.
  • Renders all output in the Light DOM for direct CSS control.
  • Ascending and descending sort order.
  • Rich text support for mentions, hashtags, and inline links using AT Protocol facet data.
  • Customizable like and reply icons via HTML attributes or CSS pseudo-elements.
  • Configurable PDS endpoint for self-hosted Bluesky instances.
  • Lazy-loaded avatar images to reduce initial page load impact.
  • Recursive rendering of nested reply threads up to a configurable depth.
  • Cancels in-flight fetch requests on element disconnect to prevent stale state overwrites.

How to use it:

1. Install & download.
# NPM
$ npm install bsky-comments
2. Load the component from a CDN:
<script type="module" src="https://unpkg.com/bsky-comments"></script>
3. Add the <bsky-comments> component to your webpage and paste the URL directly from your browser. The component resolves the handle to a DID automatically.
<!-- The post attribute accepts any standard bsky.app post URL -->
<bsky-comments
  post="https://bsky.app/profile/yourhandle.bsky.social/post/abc123xyz456"
></bsky-comments>
4. When your build process already knows the DID, pass the AT-URI via the `uri` attribute. This skips the handle-resolution API call entirely.
<!-- uri takes precedence over post when both are set -->
<!-- Use this on static sites where the DID is known at build time -->
<bsky-comments
  uri="at://did:plc:exampleDID12345/app.bsky.feed.post/abc123xyz456"
></bsky-comments>
5. Show newest comments first:
<bsky-comments
  post="https://bsky.app/profile/yourhandle.bsky.social/post/abc123xyz456"
  sort="desc"
></bsky-comments>
6. Pass raw emoji or inline SVG directly into the icon attributes:
<!-- Replace the default heart and chat-bubble icons with emoji -->
<bsky-comments
  post="https://bsky.app/profile/yourhandle.bsky.social/post/abc123xyz456"
  icon-like="💙"
  icon-reply="↩️"
></bsky-comments>

<!-- Or use inline SVG for pixel-perfect control -->
<bsky-comments
  post="https://bsky.app/profile/yourhandle.bsky.social/post/abc123xyz456"
  icon-like='<svg viewBox="0 0 24 24" width="16" height="16" fill="currentColor"><path d="M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5A5.5 5.5 0 0 1 7.5 3c1.74 0 3.41.81 4.5 2.09A6.01 6.01 0 0 1 16.5 3 5.5 5.5 0 0 1 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z"/></svg>'
></bsky-comments>
/* Hide the default emoji and replace it with a background image */
.bsky-icon-like {
  display: inline-block;
  width: 16px;
  height: 16px;
  color: transparent; /* Makes the default emoji invisible */
  background-image: url('/icons/heart.svg');
  background-size: contain;
  background-repeat: no-repeat;
}
7. All available props for the <bsky-comments> component.
  • post (string): The public web URL of the Bluesky post (e.g., https://bsky.app/profile/...). The component resolves the handle to a DID automatically via one extra API call.
  • uri (string): The internal AT-URI (e.g., at://did:plc:...). When set, this takes precedence over post and skips handle resolution.
  • sort (string): Comment sort order. Accepts asc (oldest first, default) or desc (newest first).
  • service (string): The PDS API base URL. Defaults to public.api.bsky.app. Set this for self-hosted Bluesky instances.
  • icon-like (string): Custom HTML string or emoji for the like icon. Defaults to ❤️.
  • icon-reply (string): Custom HTML string or emoji for the reply icon. Defaults to 💬.
  • depth (number): Maximum nesting depth for fetched replies. Defaults to 10.
8. Customize the component with the following CSS classes:
/* Add a left border and bottom spacing to each comment */
.bsky-comment {
  border-left: 2px solid #e5e7eb;
  padding-left: 1rem;
  margin-bottom: 1.25rem;
}

/* Style the actions row for likes and replies */
.bsky-actions {
  display: flex;
  gap: 1rem;
  font-size: 0.875rem;
  color: #6b7280;
  margin-top: 0.5rem;
}

/* Indent nested reply threads */
.bsky-replies {
  margin-top: 0.75rem;
  padding-left: 1rem;
}
9. Or via TailwindCSS:
<!-- Wrap the component and target its internal classes with Tailwind selectors -->
<div class="
  [&_.bsky-comment]:border-l-2
  [&_.bsky-comment]:border-gray-200
  [&_.bsky-comment]:pl-4
  [&_.bsky-comment]:mb-4
  [&_.bsky-actions]:text-sm
  [&_.bsky-actions]:text-gray-500
  [&_.bsky-actions]:flex
  [&_.bsky-actions]:gap-4
">
  <bsky-comments post="https://bsky.app/profile/yourhandle.bsky.social/post/abc123xyz456" />
</div>

Framework Integration

 React / Next.js: 
// Import once to register the custom element globally
import 'bsky-comments';

export function PostPage() {
  return (
    <section className="comments-section">
      {/* Use it like any other HTML element in JSX */}
      <bsky-comments post="https://bsky.app/profile/yourhandle.bsky.social/post/abc123xyz456" />
    </section>
  );
}

Framework Integration:

Vue / Nuxt:
<script setup>
// Registering the custom element on component mount
import 'bsky-comments';
</script>

<template>
  <!-- Bind the post URL from your reactive state -->
  <bsky-comments :post="currentPostUrl" />
</template>
Svelte / SvelteKit:
<script>
  // Import at the top of your layout or page file
  import 'bsky-comments';
</script>

<!-- The component renders as a standard HTML element -->
<bsky-comments post="https://bsky.app/profile/yourhandle.bsky.social/post/abc123xyz456" />
Astro:
---
// Import inside the frontmatter block for server-side bundling
import 'bsky-comments';
---

<!-- Pass a static post URL resolved at build time -->
<bsky-comments post="https://bsky.app/profile/yourhandle.bsky.social/post/abc123xyz456" />
Alternatively, use the CDN import in an Astro <script> tag:
---
---

<bsky-comments post="https://bsky.app/profile/yourhandle.bsky.social/post/abc123xyz456" />

<script>
  // Loads the component at runtime on the client side
  import 'bsky-comments';
</script>

FAQs:

Q: What is the difference between the post and uri attributes?
A: post accepts a standard https://bsky.app/... URL and resolves the handle to a DID via one extra API call. uri accepts a pre-resolved at://did:plc:... string and skips that step. Use uri on static sites where the DID is available at build time to cut the request count in half.Q: Why am I seeing a “Could not resolve handle” error?
A: The handle in the post URL no longer resolves on the public Bluesky network, usually because the account was renamed or deleted. Pass the uri attribute with the full AT-URI to bypass handle resolution.Q: Does Tailwind CSS work with this component?
A: Yes. The component renders in the Light DOM, so Tailwind’s arbitrary variant selectors (e.g., [&_.bsky-comment]:border-l-2) apply directly on the wrapper element.Q: Can I point the component at a self-hosted Bluesky instance?
A: Yes. Set the service attribute to your PDS base URL (e.g., service="https://pds.example.com/xrpc"). The component sends all API requests to that endpoint.

The post Embed Bluesky Comment Threads Anywhere With bsky-comments appeared first on CSS Script.


Discover more from RSS Feeds Cloud

Subscribe to get the latest posts sent to your email.

Leave a Reply

Your email address will not be published. Required fields are marked *

Discover more from RSS Feeds Cloud

Subscribe now to keep reading and get access to the full archive.

Continue reading