Authoring with markdown
Spark components compose into markdown via MDC (Markdown Components) — a syntax extension that lets a .md file embed Vue components alongside prose:
:realm{name="codex"} ships icons, item data, and patch diffs.
::callout{title="Heads up"}
Imports run via the `hearth` CLI, never over HTTP.
::
The parser is @nuxtjs/mdc — a standalone npm package. Spark does not ship MDC. Spark components just happen to be the most useful citizens of the namespace. This page documents the integration pattern; the parser is its own dependency.
Docs, news, patch notes, in-game lore, help drawers, About pages — anywhere you'd want to hand-author rich text and reach for a Spark component without leaving the editor. Not for gameplay surfaces; not a substitute for <template> markup in real Vue components.
What MDC is
| Form | Use | Example |
|---|---|---|
:tag{prop="val"} | inline component | :realm{name="lextris"} |
::tag{prop="val"} ... :: | block component (can wrap children) | ::callout{title="Note"}\n…body…\n:: |
| Plain markdown | unchanged | **bold**, [link](/foo) |
Component names map to Vue components by kebab-case: :spark-realm → <SparkRealm>, ::spark-flash → <SparkFlash>. Anything Vue can render — including your own components — is reachable.
Setup — Nuxt 3
If you're already using @nuxt/content, MDC is built in:
bun add @nuxt/content
export default defineNuxtConfig ({
modules: [ '@nuxt/content' ]
});
Auto-imported components from components/ are MDC-callable out of the box. Spark components imported into your app are also reachable — register them globally if you want to call them in markdown:
import { createSpark, SparkRealm, SparkFlash, SparkLogo } from '@katforge/spark';
export default defineNuxtPlugin ((nuxtApp) => {
nuxtApp.vueApp.use (createSpark ({ katforge }));
nuxtApp.vueApp.component ('SparkRealm', SparkRealm);
nuxtApp.vueApp.component ('SparkFlash', SparkFlash);
nuxtApp.vueApp.component ('SparkLogo', SparkLogo);
});
Setup — Vite + Vue 3 (no Nuxt)
For game sites and standalone Vue apps:
bun add @nuxtjs/mdc
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import Mdc from '@nuxtjs/mdc/vite';
export default defineConfig ({
plugins: [
vue (),
Mdc () // enables `import lore from './lore.md'`
]
});
import { createApp } from 'vue';
import { createSpark, SparkRealm, SparkFlash } from '@katforge/spark';
import { MDC } from '@nuxtjs/mdc/runtime';
const app = createApp (App);
app.use (createSpark ({ katforge }));
// Globally register the components you want available in markdown.
app.component ('Mdc', MDC);
app.component ('SparkRealm', SparkRealm);
app.component ('SparkFlash', SparkFlash);
app.mount ('#app');
Then render markdown anywhere:
<template>
<article>
<Mdc :value="patchNotesMarkdown" />
</article>
</template>
Spark components in markdown
Vue's kebab-case auto-resolution means every Spark component is MDC-callable as soon as it's globally registered. The most useful ones for authored content:
| Spark component | MDC tag | Inline / block | What it's for |
|---|---|---|---|
SparkRealm | :spark-realm{name="codex"} | inline | Stylized realm name with brand color |
SparkLogo | :spark-logo{realm="stumper"} | inline | Realm logo |
SparkLevelBadge | :spark-level-badge{:level="42"} | inline | Player level badge |
SparkEmberBalance | :spark-ember-balance{:amount="120"} | inline | Currency display |
SparkIngotBalance | :spark-ingot-balance{:amount="50"} | inline | Currency display |
SparkFlash | ::spark-flash{tone="ok"}\nDone\n:: | block | Inline flash message |
SparkScreenshot | :spark-screenshot{src="/img.png"} | inline | Framed screenshot |
SparkSkeleton | :spark-skeleton{...} | inline | Loading placeholder (rare in markdown) |
MDC parses bare attribute values as strings. Use :level="42" and :open="true" to pass them as actual numbers and booleans, the same way Vue's v-bind shorthand works.
Aliasing for shorter tags
If you find yourself typing :spark-realm{...} everywhere, register a thin local alias:
<script setup>
import { SparkRealm } from '@katforge/spark';
defineProps ({ name: { type: String, required: true } });
</script>
<template>
<SparkRealm :name="name" />
</template>
Now markdown reads :realm{name="codex"}. This site uses that exact pattern — <Realm> is a 3-line wrapper around <SparkRealm>, and every doc page calls :realm{...} not :spark-realm{...}.
Slots — passing children
Block-form components receive their children as the default slot:
::callout{title="Imports are CLI-only"}
There is no HTTP write endpoint. The `hearth codex import` command lands the
envelope into Postgres. **Re-running with the same patch is always a no-op.**
::
The body is parsed as markdown, including bold, links, code spans, and nested MDC blocks.
What MDC isn't
| Feature | Verdict |
|---|---|
| MDX (JSX in markdown) | no — different ecosystem, React-side. MDC is the Vue equivalent in spirit. |
| RSC / streaming | no — MDC is a runtime / build-time render, not a server-component pipeline. |
| HTML sanitization | no — @nuxtjs/mdc parses, it does not sanitize. Never feed user-authored markdown to MDC without a sanitization pass first. |
| Live reactivity for content edits | partial — Nuxt Content offers HMR; standalone @nuxtjs/mdc re-parses on :value change. |
If you let players write markdown that other players will see (lobby descriptions, custom challenge text, shouts), pass the input through a sanitizer like DOMPurify before parseMarkdown() and disable the ::component{} syntax server-side. MDC is a power tool, not a safety harness.
Ecosystem-wide today
| Project | MDC available? |
|---|---|
| katforge.com (this site) | yes — @nuxt/content |
| stumper.gg | no — would need @nuxtjs/mdc install |
| lextris.com | no — would need @nuxtjs/mdc install |
| geargoblins.com | no — would need @nuxtjs/mdc install |
Spark itself stays MDC-free on purpose. Bundling the parser into Spark would force every gameplay surface to ship unified + remark + rehype + shiki — markdown machinery that 95% of Spark's traffic never uses. The opt-in pattern above keeps Spark a clean leaf.
Reference
https://github.com/nuxtjs/mdc— standalone parser, Vite plugin, runtimehttps://content.nuxt.com— Nuxt Content (the@nuxt/contentintegration this site uses)https://github.com/syntax-tree/mdast— the AST shape under the hood, useful when writing custom transforms