Atproto AMA app
README.md

@askimut/widget #

Embeddable Askimut widgets: show your answered Q&A and accept native Questions on any site. Built as Elena Progressive Web Components — plain light-DOM elements, no Shadow DOM, no framework required.

  • <askimut-list> — renders the Recipient's answered Q&A as stacked groups (same presentation as the Askimut profile), each card linking to its Askimut permalink.
  • <askimut-ask> — the ask slot: sign-in button (OAuth popup on Askimut), ask form, or nothing when the Recipient's Questions are closed. Submits native (askimut Source) Questions.
  • fetchQuestions / fetchQuestion — a headless API client that fetches the same data without rendering any UI.

Both are composed inside a plain <askimut> wrapper element (not a registered custom element) that scopes the Recipient and theme to its children.

CDN quick start #

Drop this into any static page — no bundler. Replace the handle and the example origin with your own; the URL is stable on the Askimut static origin:

<link rel="stylesheet" href="https://askimut.example.com/widget/widget.css" />
<script type="module" src="https://askimut.example.com/widget/widget.js"></script>

<askimut recipient="you.example.com">
  <askimut-list></askimut-list>
  <askimut-ask></askimut-ask>
</askimut>

The script registers askimut-list and askimut-ask on import. With a CDN embed you don't need the origin attribute: the widget defaults to the origin the bundle itself was loaded from.

npm quick start #

For bundler-based sites (React, Astro, Svelte, …):

pnpm add @askimut/widget
// Importing the module registers both custom elements (it calls define() for you).
import "@askimut/widget";
// Named imports skip registration — call define() yourself if you use them:
import { AskimutList, AskimutAsk } from "@askimut/widget";
// AskimutList.define();
// AskimutAsk.define();
<askimut recipient="you.example.com" origin="https://askimut.example.com">
  <askimut-list></askimut-list>
  <askimut-ask></askimut-ask>
</askimut>

When the bundle ships with the host page (npm embed), set origin explicitly — the default (the bundle's own origin) would incorrectly point at your site.

Headless API client #

The package also ships a data-only client for the public Widget API — the same reads the widgets perform, with no rendering, no components, and no DOM required. Useful for SSR, static-site generation, or custom UIs.

// npm — imports nothing visual, registers no elements:
import { fetchQuestions, fetchQuestion } from "@askimut/widget/api";
// CDN — the same module standalone:
// import { fetchQuestions, fetchQuestion } from "https://askimut.example.com/widget/api.js";
// A Recipient's answered Q&A, grouped exactly as <askimut-list> renders it:
const { handle, groups } = await fetchQuestions("you.example.com", {
  origin: "https://askimut.example.com",
});
// groups: [{ primary, similar, hiddenCount }] — primary.answer.answerContent.content
// holds the Answer; `handle` (from the API) builds /{handle}/{questionId} permalinks.

// One Question by id — the same public record the /{handle}/{questionId}
// permalink page shows: answered or pending (answer: null), never dismissed
// or deleted, Unavailable Asker redaction applied:
const question = await fetchQuestion("you.example.com", "q-123", {
  origin: "https://askimut.example.com",
});
  • The origin option defaults to the origin the module itself was loaded from — convenient on the CDN, wrong for bundler imports, where you should set it explicitly (same rule as the wrapper's origin attribute).
  • Any failure throws an AskimutApiError with the HTTP status (0 for network failures), the request url, and the server's error message; a missing recipient/questionId throws a TypeError.
  • Pending Questions from Blocked or Suspended askers are hidden, matching the widget list's moderation behavior.

The client is also available as named exports of the main bundle — import { fetchQuestions } from "@askimut/widget" or from the CDN widget.js — at the cost of registering the custom elements on import.

Wrapper attributes #

Attribute Required Meaning
recipient yes Askimut handle or DID. Children share this context.
origin no Askimut origin for API calls, OAuth popup, and permalinks. Defaults to the origin the widget bundle was loaded from.
unstyled no Boolean. Removes Askimut branding — see below.

Children are explicit and order is yours: interleave the widget parts with your own content, or use only one of them.

Styling: styled vs unstyled #

Styled (default). The wrapper defines Askimut design tokens as CSS custom properties, which the descendants consume:

askimut {
  --askimut-surface: #ffffff; /* card / input background */
  --askimut-ink: #1c1917; /* primary text */
  --askimut-muted: #78716c; /* secondary text */
  --askimut-border: #e7e5e4; /* card / input border */
  --askimut-accent: #2c5ea8; /* link + button accent */
  --askimut-radius: 8px; /* corner radius */
  --askimut-stack-offset: 8px; /* stacked-card peek offset */
  --askimut-font: "IBM Plex Sans", sans-serif;
  --askimut-mono: "IBM Plex Mono", monospace;
}

Override any of them on the wrapper (inline or in your stylesheet) to re-theme.

Unstyled. Add the boolean unstyled attribute and Askimut branding goes away:

<askimut recipient="you.example.com" unstyled>
  <askimut-list></askimut-list>
  <askimut-ask></askimut-ask>
</askimut>

Layout stays (block flow, stacked cards), the --askimut-* hooks remain, and their defaults turn neutral: fonts and text colors inherit from your page, backgrounds and borders go transparent, and buttons fall back to the browser's native look. Style the light-DOM internals directly — e.g. askimut-list .card { … } — or set tokens on the wrapper for finer control.

Components and API #

The Custom Elements Manifest (custom-elements.json, published in the package and served alongside the bundle) documents props, events, and CSS custom properties for both elements.