superlore

Render in your app

Render a superlore MDX string live inside any app with superlore/runtime — the same components, Canvas, and code highlighting as a published page.

The docs site compiles MDX at build time. A companion app that stores documents as strings needs to compile and render them at runtime — so superlore ships superlore/runtime: give it an MDX string, get the rendered document, with the full component set, Canvas, and Shiki code highlighting.

Loading canvas…

Install

One package, one stylesheet. The stylesheet is precompiled — it carries the theme, the component styles, and code highlighting, so your app needs no Tailwind.

npm install superlore
// once, anywhere — every rule is scoped to .superlore-doc, so an app-wide import is inert
// outside the container (no reset of your *, html, body; no token or utility collisions).
import "superlore/runtime.css";

Self-scoped — safe to import app-wide

Every selector in superlore/runtime.css is rooted at a .superlore-doc container. Import it once, app-wide, and it changes zero pixels outside a superlore doc — it never touches your :root tokens, body, or utility classes. SuperloreDoc renders the .superlore-doc wrapper for you; don't add your own.

Dark mode rides the container

Light and dark are co-equal. Dark mode is container-local: it rides data-theme="dark" on the .superlore-doc element (set it with <SuperloreDoc theme="dark" />), not a class on <html>. So a doc can render dark while your app is light — and two docs can show different themes on one page.

Render a doc

SuperloreDoc is the one-line entry point — pass the MDX string, get the rendered document.

"use client";
import { SuperloreDoc } from "superlore/runtime";

export function Doc({ mdx }: { mdx: string }) {
  return <SuperloreDoc source={mdx} />;
}

That's the same renderer the docs site and the Viewer use — a Timeline, a Board, a DataTable, or a superlore-canvas fence all render exactly as they would on a published page.

Match your brand

By default a doc renders in superlore's violet. Pass tokens to render in your palette instead — links, accents, focus rings, and surfaces all follow your tokens. Pair it with theme to pick light/dark per doc.

"use client";
import { SuperloreDoc } from "superlore/runtime";

export function Doc({ mdx, dark }: { mdx: string; dark?: boolean }) {
  return (
    <SuperloreDoc
      source={mdx}
      theme={dark ? "dark" : "light"}
      tokens={{ accent: "var(--brand)", accentText: "var(--brand)" }}
    />
  );
}

Pass plain colors, or — better — your own CSS variables (var(--brand)). Because the tokens resolve as CSS custom properties, your app's existing light/dark switch re-colors the doc for free, with nothing to re-pass on a theme change.

Wrapping several docs? SuperloreTheme applies the same tokens/theme to every SuperloreDoc inside it:

import { SuperloreDoc, SuperloreTheme } from "superlore/runtime";

<SuperloreTheme tokens={{ accent: "var(--brand)" }} theme={dark ? "dark" : "light"}>
  <SuperloreDoc source={a} />
  <SuperloreDoc source={b} />
</SuperloreTheme>;
TokenDrives
accentlinks, active state, focus ring (and fumadocs --color-fd-primary)
accentText / accentInkaccent text on a surface; text/icon on the accent fill
accentMuted / accentBorder / accentHoversubtle accent background; accent outline; hover
surface / surface2 / backgroundcard, nested/hover surface, page background
text / text2 / text3primary, secondary, muted text
border / borderSubtledefault border; separators
success / warning / dangerstatus colors (callouts, board lanes)
varsescape hatch — set any --kp-* / --color-fd-* variable directly

It's just CSS variables

tokens (on SuperloreDoc or SuperloreTheme) is sugar — it writes these as custom properties on the .superlore-doc element itself. You can set the same --kp-* / --color-fd-* variables on that element yourself and get the identical result, so it composes with whatever theming your app has.

Lower-level: the hook and the compiler

Need the parsed frontmatter, your own loading state, or a custom wrapper? Use the hook, or call the compiler directly. With the lower-level API you render the Content yourself, so wrap it in a .superlore-doc element (data-theme="dark" for dark) — that's the container superlore/runtime.css is scoped to. SuperloreDoc does this for you.

"use client";
import { useSuperloreMdx } from "superlore/runtime";

function Doc({ mdx }: { mdx: string }) {
  const { Content, frontmatter, error } = useSuperloreMdx(mdx);
  if (!Content) return <Spinner />;
  return (
    <article className="superlore-doc">
      <h1>{frontmatter.title as string}</h1>
      <Content />
      {error && <p role="alert">{error}</p>}
    </article>
  );
}
import { compileMdxSource } from "superlore/runtime";

// extend the pipeline — add your own remark/rehype plugins or component overrides
const { Content, frontmatter } = await compileMdxSource(mdx, {
  components: { Callout: MyCallout },
  rehypePlugins: [myAnchorPlugin],
});

What you get

Identical to the build
The same components, Canvas, and Shiki code highlighting as a published superlore page.
No Tailwind required
superlore/runtime.css is precompiled and self-contained — import it and you're done.
Client-safe
Compiles MDX in the browser, so it drops into a client component in any React app.
Extensible
Override components and append remark/rehype plugins via compileMdxSource options.

superlore/runtime evaluates MDX at runtime, so render it in a client component ("use client"). It's the one piece that runs client-side; everything else (the MCP, the content model) stays where it belongs.

On this page

Built withsuperlore