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.
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>;| Token | Drives |
|---|---|
accent | links, active state, focus ring (and fumadocs --color-fd-primary) |
accentText / accentInk | accent text on a surface; text/icon on the accent fill |
accentMuted / accentBorder / accentHover | subtle accent background; accent outline; hover |
surface / surface2 / background | card, nested/hover surface, page background |
text / text2 / text3 | primary, secondary, muted text |
border / borderSubtle | default border; separators |
success / warning / danger | status colors (callouts, board lanes) |
vars | escape 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.
Add it to an existing app
Wire superlore into an app you already have — install the package, import the theme, register the components, define the content source, and mount the MCP. Every import comes from superlore.
Troubleshooting
Fix the common snags when running a superlore KB — auth not gating, the MCP returning not_found, and Tailwind not styling superlore components.