npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

remark-mermaid-tldraw

v0.1.0

Published

Render mermaid diagrams as tldraw's hand-drawn SVGs at build time — a remark plugin + headless renderer with light/dark variants and content-hash caching.

Downloads

8

Readme

remark-mermaid-tldraw

Render mermaid diagrams as tldraw's hand-drawn SVGs, at build time. A remark plugin rewrites your ```mermaid fences; a headless renderer turns each one into a pair of light/dark, transparent SVGs using a real tldraw editor. Zero client JS, no runtime mermaid — just two <img> tags per diagram.

npm · GitHub · detailed guide

Mermaid's default output is fine, but it's flat — boxes, straight lines, a stock look. tldraw renders the same graph definition in its warm, hand-drawn style, and because it happens during your build, the visitor never downloads mermaid or runs a layout pass. They get an SVG.

This generalizes Sunil Pai's Astro mermaid + tldraw plugin (which he shared on X and in his repo) into a framework-agnostic package, and stands on @tldraw/mermaid.

Install

npm i -D remark-mermaid-tldraw tldraw @tldraw/mermaid playwright react react-dom
npx playwright install chromium

tldraw, @tldraw/mermaid, playwright, react, and react-dom are peer dependencies — the renderer drives a real tldraw editor in headless Chromium, so you bring the browser and the React/tldraw stack. (vite, @vitejs/plugin-react, tinyglobby, unist-util-visit, and mdast-util-from-markdown ship as regular deps.)

Quick start

There are four pieces. The first three you do once; the fourth is every diagram.

1. Add the remark plugin

It rewrites each ```mermaid fence into a <figure> with a light and a dark <img>. Wire it into whatever turns your markdown/MDX into HTML.

// e.g. a Vite + @mdx-js/rollup config
import mdx from "@mdx-js/rollup";
import remarkMermaidTldraw from "remark-mermaid-tldraw";

export default {
  plugins: [
    mdx({ remarkPlugins: [remarkMermaidTldraw] }),
  ],
};

2. Add the render step

The plugin only points at SVGs — something has to produce them. Pick one:

CLI prebuild (works with any framework). Scan your content, write the SVGs, then build:

// package.json
{
  "scripts": {
    "prebuild": "remark-mermaid-tldraw --content \"src/**/*.{md,mdx}\" --out public/diagrams",
    "build": "..."
  }
}

Astro integration (auto-renders on config:setup, watches in dev):

// astro.config.mjs
import { defineConfig } from "astro/config";
import remarkMermaidTldraw from "remark-mermaid-tldraw";
import { mermaidTldraw } from "remark-mermaid-tldraw/astro";

export default defineConfig({
  integrations: [mermaidTldraw()],
  markdown: { remarkPlugins: [remarkMermaidTldraw] },
});

3. Add the CSS

The light SVG shows by default; the dark one swaps in under a dark color scheme. You control the switch:

.mermaid-dark { display: none; }
@media (prefers-color-scheme: dark) {
  .mermaid-light { display: none; }
  .mermaid-dark { display: inline; }
}
/* or, for a class-based theme:
   html.dark .mermaid-light { display: none; }
   html.dark .mermaid-dark  { display: inline; } */

.mermaid-diagram { margin: 1.5rem auto; }
.mermaid-diagram img { width: 100%; height: auto; }

4. Write a mermaid fence

```mermaid
flowchart LR
  A[Idea] --> B{Worth it?}
  B -- yes --> C[Build]
  B -- no  --> D[Shelve]
```

Cap a diagram's width with fence meta (handy for tall top-down flowcharts):

```mermaid width=380
flowchart TD
  Start --> Step1 --> Step2 --> Done
```

How it works

The two halves never talk directly — they agree on filenames.

  1. The remark plugin hashes the fence's source (sha256(trimmed).slice(0,16)) and emits <img src="/diagrams/<hash>.svg"> + <img src="/diagrams/<hash>.dark.svg">. The filename depends only on the diagram source.
  2. The renderer scans the same content, hashes each block the same way, and — for anything not already rendered — boots a transient Vite dev server, opens a tldraw editor in headless Chromium, runs @tldraw/mermaid's createMermaidDiagram, and exports the layout twice (light + dark theme, transparent background).

Because the URL is a pure function of the source, the plugin and renderer line up with no shared manifest. Caching works off a version marker written inside each SVG: change the diagram source and the filename changes (missing file → render); change the render logic and the marker goes stale (stale marker → re-render in place, same URL). The browser only launches when something is actually pending.

Diagram types tldraw can't model natively (pie, gantt, class, ER, …) fall back to mermaid's own SVG — still a real diagram, just not the hand-drawn aesthetic.

See GUIDE.md for the full architecture, the caching model, framework recipes, CI setup, and troubleshooting.

Options

Remark plugin — RemarkMermaidTldrawOptions

remarkPlugins: [[remarkMermaidTldraw, { publicPrefix: "/diagrams" }]]

| option | type | default | what it does | |--------|------|---------|--------------| | publicPrefix | string | "/diagrams" | URL prefix the SVGs are served from. Must match where the renderer writes them (a public/diagrams outDir is served at /diagrams). | | figureClassName | string | "mermaid-diagram" | class on the wrapping <figure> | | lightClassName | string | "mermaid-light" | class on the light <img> | | darkClassName | string | "mermaid-dark" | class on the dark <img> | | alt | string | "Diagram" | alt text on both images | | notProse | boolean | true | append not-prose to the figure class (keeps Tailwind Typography from restyling it) | | theme | "both" \| "light" \| "dark" | "both" | which variants to emit. "both" ships a light and a dark <img> to swap in CSS; on a single-theme site, emit just one so the browser never fetches the variant it can't show. |

Renderer — RenderOptions

Passed to renderDiagrams(), the Astro integration, and (a subset) the CLI.

| option | type | default | what it does | |--------|------|---------|--------------| | content | string \| string[] | ["src/**/*.md", "src/**/*.mdx"] | globs to scan for mermaid blocks | | cwd | string | process.cwd() | directory globs resolve against | | outDir | string | "public/diagrams" | where SVGs are written | | padding | number | 16 | padding (px) around the shapes in the exported SVG | | log | (message: string) => void | console.log | log callback | | mountTimeout | number | 30000 | ms to wait for the tldraw editor to mount |

renderDiagrams() returns { total, rendered } — the number of distinct diagrams found, and how many were (re)rendered this run.

CLI

remark-mermaid-tldraw [render] [options]

  -c, --content <glob>   markdown/MDX glob to scan (repeatable)
                         default: src/**/*.md, src/**/*.mdx
  -o, --out <dir>        output directory for SVGs   (default: public/diagrams)
  -p, --padding <px>     padding around shapes in the SVG   (default: 16)
  -w, --watch            re-render on content changes
      --clean            prune SVGs no diagram references anymore, then render
  -h, --help             show this help
remark-mermaid-tldraw --content "src/content/**/*.md" --out public/diagrams
remark-mermaid-tldraw --watch
remark-mermaid-tldraw --clean

render is an optional verb; remark-mermaid-tldraw and remark-mermaid-tldraw render do the same thing.

Fence meta

| meta | example | effect | |------|---------|--------| | width | ```mermaid width=380 | caps the figure's max-width. Bare numbers become px (380 → 380px); any CSS length passes through (width=24rem). |

Caveats

  • Build-time only. The renderer needs a browser. In CI, run npx playwright install chromium before building (see GUIDE.md).
  • Commit or generate the SVGs. They live in outDir (default public/diagrams). Either commit them, or make rendering a prebuild step so they exist before the build runs. The Astro integration handles this for you.
  • Filenames are never auto-pruned on a normal render — a framework's content cache can still point at an old filename, and deleting it would 404. Run --clean (or pruneOrphans()) when you want to GC stale SVGs.
  • Unsupported diagram types (pie, gantt, class, ER, …) fall back to mermaid's own SVG, so they won't match the hand-drawn look.
  • Large diagrams render fine but can produce big SVGs; cap their width with fence meta.

Credits

License

MIT © Sean Geng