Embeddable memo cards for Memos, delivered as a website and npm packages.
- Rich memo cards with themes, density presets, and extendable design tokens
- Core HTML renderer for SSR and static-site workflows
- Batch multi-memo fetching and shared-style list rendering for note digests and weekly roundups
- Shared memo client for cross-embed request deduping on React and MDX pages
- React components for single embeds and multi-memo roundups, with optional pre-fetched memo rendering
- Web Component wrapper with exposed
::part(...)hooks - Iframe embed route for no-build integrations
- Lightweight markdown support for headings, lists, task lists, quotes, links, and fenced code blocks
- Attachment previews for images and grouped reaction badges
- Optional auto-resizing iframe snippets via
postMessage
apps/site: TanStack Start website (docs, playground, iframe embeds)packages/memos-embed: core API + SSR HTML helperspackages/memos-embed-react: React component wrapperpackages/memos-embed-wc: Web Component wrapper
pnpm install
pnpm devOpen:
- site:
http://localhost:3000 - playground:
http://localhost:3000/playground
pnpm -r buildpnpm test
pnpm test:artifactspnpm validate- Add a changeset for package changes with
pnpm changeset - The
Publish Packagesworkflow versions changed packages directly onmain, publishes them to npm, then creates a Git tag and a GitHub Release generated bychangelogithub - If your
mainbranch blocks workflow pushes or release creation, add aCHANGESETS_GITHUB_TOKENsecret with repo write access - Set
NPM_TOKENso package publishing can authenticate with npm
baseUrl accepts any of these forms and normalizes them internally:
https://demo.usememos.comhttps://demo.usememos.com/apihttps://demo.usememos.com/api/v1
Use whichever shape you already have. The client resolves memo and user requests against /api/v1 automatically.
import { extendTheme, fetchMemo, renderMemoHtmlSnippet } from 'memos-embed'
const memo = await fetchMemo({
baseUrl: 'https://demo.usememos.com/api/v1',
memoId: '1',
})
const blogTheme = extendTheme('paper', {
radius: 'var(--radius)',
fontFamily: 'inherit',
tokens: {
background: 'var(--card)',
foreground: 'var(--card-foreground)',
border: 'var(--border)',
accent: 'var(--primary)',
accentForeground: 'var(--primary-foreground)',
mutedForeground: 'var(--muted-foreground)',
codeBackground: 'var(--muted)',
},
})
const html = renderMemoHtmlSnippet(memo, {
includeStyles: true,
theme: blogTheme,
density: 'comfortable',
showAttachments: true,
showReactions: true,
linkTarget: '_blank',
})import { fetchMemos, renderMemoListHtmlSnippet } from 'memos-embed'
const memos = await fetchMemos({
baseUrl: 'https://demo.usememos.com/api/v1',
memoIds: ['1', '2', '3'],
})
const html = renderMemoListHtmlSnippet(memos, {
layout: 'stack',
gap: '20px',
theme: 'paper',
})import { createMemoClient } from 'memos-embed'
const memoClient = createMemoClient()import { MemoEmbed } from '@memos-embed/react'
<MemoEmbed
baseUrl="https://demo.usememos.com/api/v1"
memoId="1"
theme="glass"
density="compact"
linkTarget="_blank"
showAttachments
showReactions
/>import { fetchMemo } from 'memos-embed'
import { MemoEmbed } from '@memos-embed/react'
const memo = await fetchMemo({
baseUrl: 'https://demo.usememos.com/api/v1',
memoId: '1',
})
<MemoEmbed memo={memo} />import { MemoEmbedList } from '@memos-embed/react'
<MemoEmbedList
baseUrl="https://demo.usememos.com/api/v1"
memoIds={["1", "2", "3"]}
layout="stack"
gap="20px"
theme="paper"
/>import { createMemoClient } from 'memos-embed'
import { MemoClientProvider, MemoEmbed, MemoEmbedList } from '@memos-embed/react'
const client = createMemoClient()
<MemoClientProvider client={client}>
<MemoEmbed baseUrl="https://demo.usememos.com/api/v1" memoId="1" />
<MemoEmbedList baseUrl="https://demo.usememos.com/api/v1" memoIds={["2", "3"]} />
</MemoClientProvider>Use a shared client when multiple embeds on one page should reuse memo and creator fetches instead of issuing duplicate requests.
import { createMemoClient, fetchMemo, fetchMemos } from 'memos-embed'
import { MemoClientProvider, MemoEmbed, MemoEmbedList } from '@memos-embed/react'
const client = createMemoClient()
const [heroMemo, roundupMemos] = await Promise.all([
fetchMemo({
baseUrl: 'https://demo.usememos.com/api/v1',
memoId: '1',
}),
fetchMemos({
baseUrl: 'https://demo.usememos.com/api/v1',
memoIds: ['2', '3'],
}),
])
<MemoClientProvider client={client}>
<MemoEmbed
baseUrl="https://demo.usememos.com/api/v1"
memo={heroMemo}
/>
<MemoEmbedList
baseUrl="https://demo.usememos.com/api/v1"
memos={roundupMemos}
/>
</MemoClientProvider>Passing memo or memos while a MemoClientProvider is active primes the shared client cache, so later embeds for the same ids can reuse already-fetched data.
<script type="module" src="https://unpkg.com/@memos-embed/wc@latest/dist/register.js"></script>
<memos-embed
base-url="https://demo.usememos.com/api/v1"
memo-id="1"
theme="midnight"
link-target="_blank"
show-tags="true"
show-attachments="true"
show-reactions="true"
></memos-embed>memos-embed::part(container) {
border-radius: 24px;
}import { renderIframeHtml } from 'memos-embed'
const iframe = renderIframeHtml({
embedBaseUrl: 'https://your-site.com',
baseUrl: 'https://demo.usememos.com/api/v1',
memoId: '1',
height: 240,
autoResize: true,
})autoResize works through a postMessage handshake keyed by frameId. renderIframeHtml() keeps that wiring aligned for you. If you hand-roll the iframe markup, make sure the iframe element id matches the frameId query param you send to /embed/:memoId.
For pages with multiple embeds, give each iframe a distinct frameId to keep resize events scoped correctly.
examples/next-mdx: Next.js App Router + server-fetched memo dataexamples/mdx-components: reusable MDX component pattern for React-based blogsexamples/astro-blog: Astro + MDX blog usageexamples/static-html: iframe and Web Component copy-paste examples for static sites and CMS pages
- The site uses source aliases so local changes in
packages/*show up immediately inapps/site - The playground keeps its configuration in the URL for easy sharing
- Package builds are powered by
tsup; site builds use Vite + TanStack Start - CI runs
pnpm validate, including lint, Biome checks, tests, package-consumer smoke tests, and the site build - Releases are managed with Changesets on
main: the workflow versions packages, commits the release changes, publishes to npm, and creates a Git tag plus achangelogithub-powered GitHub Release automatically
