Fumadocs

Mermaid

Rendering diagrams in your docs

Setup

Fumadocs doesn't have a built-in Mermaid wrapper provided, we recommend using mermaid directly.

You can decide the Mermaid renderer to configure:

Official Renderer

npm install mermaid

Create the Mermaid component:

components/mdx/mermaid.tsx
'use client';

import { use, useId, useSyncExternalStore } from 'react';

export function Mermaid({ chart }: { chart: string }) {
  // `false` on the server and during hydration
  const isClient = useSyncExternalStore(
    () => () => {},
    () => true,
    () => false,
  );

  if (!isClient) return;
  return <MermaidContent chart={chart} />;
}

const cache = new Map<string, Promise<unknown>>();

function cachePromise<T>(key: string, setPromise: () => Promise<T>): Promise<T> {
  const cached = cache.get(key);
  if (cached) return cached as Promise<T>;

  const promise = setPromise();
  cache.set(key, promise);
  return promise;
}

function subscribeTheme(onChange: () => void) {
  const observer = new MutationObserver(onChange);
  observer.observe(document.documentElement, { attributeFilter: ['class'] });
  return () => observer.disconnect();
}

function MermaidContent({ chart }: { chart: string }) {
  const id = useId();
  const theme = useSyncExternalStore(subscribeTheme, () =>
    document.documentElement.classList.contains('dark') ? 'dark' : 'default',
  );
  const { default: mermaid } = use(cachePromise('mermaid', () => import('mermaid')));

  mermaid.initialize({
    startOnLoad: false,
    securityLevel: 'loose',
    fontFamily: 'inherit',
    themeCSS: 'margin: 1.5rem auto 0;',
    theme,
  });

  const { svg, bindFunctions } = use(
    cachePromise(`${chart}-${theme}`, () => {
      return mermaid.render(id, chart.replaceAll('\\n', '\n'));
    }),
  );

  return (
    <div
      ref={(container) => {
        if (container) bindFunctions?.(container);
      }}
      dangerouslySetInnerHTML={{ __html: svg }}
    />
  );
}

Pele

Pele is a lightweight Mermaid renderer that outputs themeable SVG.

npm install pele
components/mdx/mermaid.tsx
import { useId } from 'react';
import { CodeBlock, Pre } from 'fumadocs-ui/components/codeblock';
import { render } from 'pele';

export function Mermaid({ chart }: { chart: string }) {
  const id = useId();

  try {
    // also lay out the chart for phones, the container query shows the one that fits
    const wide = render(chart, { idPrefix: id });
    const narrow = render(chart, { idPrefix: `${id}narrow`, maxWidth: 320 });

    if (narrow.width >= wide.width) return <div dangerouslySetInnerHTML={{ __html: wide.svg }} />;

    return (
      <div className="@container">
        <div className="@max-[600px]:hidden" dangerouslySetInnerHTML={{ __html: wide.svg }} />
        <div className="@min-[600px]:hidden" dangerouslySetInnerHTML={{ __html: narrow.svg }} />
      </div>
    );
  } catch {
    return (
      <CodeBlock title="Mermaid">
        <Pre>{chart}</Pre>
      </CodeBlock>
    );
  }
}

Map its fonts and colors to Fumadocs, so diagrams follow light/dark mode:

app/global.css
.pele {
  --pele-font: var(--font-sans);
  --pele-font-mono: var(--font-mono);
  --pele-bg: var(--color-fd-background);
  --pele-fg: var(--color-fd-foreground);
  --pele-muted: var(--color-fd-muted-foreground);
  --pele-line: var(--color-fd-muted-foreground);
  --pele-surface: var(--color-fd-card);
  --pele-surface-alt: var(--color-fd-secondary);
  --pele-border: var(--color-fd-border);
  --pele-accent: var(--color-fd-primary);
}

Beautiful Mermaid

beautiful-mermaid is a 3rd party Mermaid renderer.

npm install beautiful-mermaid
components/mdx/mermaid.tsx
import { CodeBlock, Pre } from 'fumadocs-ui/components/codeblock';
import { renderMermaidSVG } from 'beautiful-mermaid';

export function Mermaid({ chart }: { chart: string }) {
  try {
    const svg = renderMermaidSVG(chart, {
      bg: 'var(--color-fd-background)',
      fg: 'var(--color-fd-foreground)',
      interactive: true,
      transparent: true,
    });

    return <div dangerouslySetInnerHTML={{ __html: svg }} />;
  } catch {
    return (
      <CodeBlock title="Mermaid">
        <Pre>{chart}</Pre>
      </CodeBlock>
    );
  }
}

Usage

Add the component as a MDX component:

components/mdx.tsx
import defaultMdxComponents from 'fumadocs-ui/mdx';
import { Mermaid } from '@/components/mdx/mermaid'; 
import type { MDXComponents } from 'mdx/types';

export function getMDXComponents(components?: MDXComponents) {
  return {
    ...defaultMdxComponents,
    Mermaid, 
    ...components,
  } satisfies MDXComponents;
}

Then, use it in MDX files.

<Mermaid
  chart="
graph TD;
subgraph AA [Consumers]
A[Mobile app];
B[Web app];
C[Node.js client];
end
subgraph BB [Services]
E[REST API];
F[GraphQL API];
G[SOAP API];
end
Z[GraphQL API];
A --> Z;
B --> Z;
C --> Z;
Z --> E;
Z --> F;
Z --> G;"
/>
ConsumersServicesServicesMobile appWeb appNode.js clientREST APIGraphQL APISOAP APIGraphQL API
ConsumersServicesServicesMobile appWeb appNode.js clientREST APIGraphQL APISOAP APIGraphQL API

As CodeBlock

You can convert mermaid codeblocks into the MDX usage with the remarkMdxMermaid plugin.

source.config.ts
import { remarkMdxMermaid } from 'fumadocs-core/mdx-plugins';
import { defineConfig } from 'fumadocs-mdx/config';

export default defineConfig({
  mdxOptions: {
    remarkPlugins: [remarkMdxMermaid],
  },
});
```mermaid
graph TD;
  A-->B;
  A-->C;
```

How is this guide?

Last updated on