Content
Markdown authoring
The prose dialect that markdown() understands, covering containers, tab groups, fence meta and diff markers. Content files need no imports, because the compiler injects whatever a page uses.
markdown() compiles .svx / .md files with an opinionated dialect on top of CommonMark: admonition containers, markdown-native tabs, and a fence pipeline with pluggable stages. Authors write no imports; when a page uses a tab group, for example, the compiler injects the components into that page’s module scope. Everything on this page is rendered by the dialect it documents.
This page is for people who write content. To wire markdown() into your app, see Content collections, and for the code() macro, which renders snippets from components, see code.
Containers
The dialect supports VitePress-compatible admonitions (tip, warning, danger, info, note, caution and important) as well as the collapsible details. Each takes an optional title on the opening line:
::: warning Heads up
Props must be serializable.
:::Heads up
Props must be serializable. (This box is the example above, rendered.)
Blank lines inside are optional, so VitePress-style tight blocks work, and a literal ::: inside a fenced code block is left alone. details renders as a native <details> disclosure:
What it expands into
An .og-admonition wrapper that the theme styles. Markdown keeps rendering inside it, including code, links and lists.
Tab groups
There are two syntaxes for tabs. Both are rewritten to <TabGroup> / <Tab>, and the components are injected for you.
::: code-group takes one fence per tab, with the label in [brackets] after the language:
::: code-group
```bash [npm]
npm i ogygia
```
```bash [pnpm]
pnpm add ogygia
```
:::npm i ogygiapnpm add ogygia::: tabs takes any markdown per tab, with one tab per == Label line:
::: tabs
== On the server
Query the brain in a server load.
== In a worker
Ship the static index; the worker filters by scope.
:::Groups with the same labels stay in sync: pick pnpm once and every install block follows, and the choice persists across pages (in localStorage). You can try it on the group above.
Fence meta
The infostring after the language goes through the fence pipeline (line highlighting, filenames, twoslash), and it works the same way for a markdown fence and a code() call:
```ts title=src/lib/site.server.tsputs a filename in the code block’s header, to the left of the copy and permalink actions. This is the common case. When a fence gives no filename, the header falls back to the language name, so it is never empty.title=reads to the end of the token, so paths containing/,.and+page-style route files work as written.```ts {2-4}highlights lines 2–4, usingtransformerMetaHighlightfrom@shikijs/transformers.```ts twoslashadds type-aware hovers through the twoslash transformer./// file: app.tson the first line gives the same filename as a special comment that is stripped from the rendered code (by theslash_meta()parser). Prefertitle=unless you are porting content that already uses the comment form.
Diff markers
Two dialects for “change this code” prose ship as fence-pipeline transformers. The line-level dialect marks a whole line with a +++ / --- prefix, and the marker never reaches the reader:
```js
--- export let count;
+++ let { count } = $props();
```export let count;
let { count } = $props();The inline dialect wraps a span inside a line in +++added+++ / ---removed---. It is the dialect svelte.dev uses, and it works together with twoslash:
```js
let +++doubled = $derived(count * 2)+++;
```let doubled = $derived(count * 2);Wire them once in your app config. They have no effect on fences that carry no marker:
import { content } from '@ogygia/content/vite';
import { diff_markers, inline_markers } from '@ogygia/content/markdown';
plugins: [ogygia(), content({ markdown: { code: { transformers: [diff_markers(), inline_markers()] } } }), sveltekit()]Where each dialect is off
Line markers skip languages in which a leading --- is real syntax (yaml, md, diff). Inline markers never treat a --- alone on its line as a delimiter, so frontmatter shown inside a fence renders untouched. For those cases, use // [!code ++] comments from @shikijs/transformers.
Extending the pipeline
Each stage above is a value you pass rather than a flag you set. transformers follow Shiki’s ecosystem contract (@shikijs/transformers, twoslash, the diff markers, or your own), meta parsers extend the infostring, and variants turn one authored fence into switchable versions (such as a JS↔TS converter or package-manager tabs). The prose features (containers, tabs, heading ids and anchors, code ids) are on by default, and each can be turned off with one key. The full option list is in the API reference.
Do / don’t
- Do write with the dialect, because tabs and admonitions carry structure that plain paragraphs lose.
- Do keep install commands in a
code-group, so readers pick their package manager once and every block on the page follows. - Don’t import
TabGroupby hand in a content page. The compiler injects it exactly when the syntax appears, and your own import only duplicates it. - Don’t fake diffs with
difffences when you mean “edit this code”. The diff language loses real syntax highlighting, while the markers keep the Shiki colours and strip themselves out.