Docs/Authoring with markdown

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:

mdc
: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

FormUseExample
:tag{prop="val"}inline component:realm{name="lextris"}
::tag{prop="val"} ... ::block component (can wrap children)::callout{title="Note"}\n…body…\n::
Plain markdownunchanged**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:

shell
bun add @nuxt/content
TypeScript
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:

TypeScript
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:

shell
bun add @nuxtjs/mdc
TypeScript
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'`
   ]
});
TypeScript
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:

Vue
<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 componentMDC tagInline / blockWhat it's for
SparkRealm:spark-realm{name="codex"}inlineStylized realm name with brand color
SparkLogo:spark-logo{realm="stumper"}inlineRealm logo
SparkLevelBadge:spark-level-badge{:level="42"}inlinePlayer level badge
SparkEmberBalance:spark-ember-balance{:amount="120"}inlineCurrency display
SparkIngotBalance:spark-ingot-balance{:amount="50"}inlineCurrency display
SparkFlash::spark-flash{tone="ok"}\nDone\n::blockInline flash message
SparkScreenshot:spark-screenshot{src="/img.png"}inlineFramed screenshot
SparkSkeleton:spark-skeleton{...}inlineLoading 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:

Vue
<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:

mdc
::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

FeatureVerdict
MDX (JSX in markdown)no — different ecosystem, React-side. MDC is the Vue equivalent in spirit.
RSC / streamingno — MDC is a runtime / build-time render, not a server-component pipeline.
HTML sanitizationno — @nuxtjs/mdc parses, it does not sanitize. Never feed user-authored markdown to MDC without a sanitization pass first.
Live reactivity for content editspartial — 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

ProjectMDC available?
katforge.com (this site)yes — @nuxt/content
stumper.ggno — would need @nuxtjs/mdc install
lextris.comno — would need @nuxtjs/mdc install
geargoblins.comno — 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, runtime
  • https://content.nuxt.com — Nuxt Content (the @nuxt/content integration this site uses)
  • https://github.com/syntax-tree/mdast — the AST shape under the hood, useful when writing custom transforms