Markdown
Add .md files to pages/ for Markdown routes. Pages inherit layouts and render as HTML, with JavaScript only for islands, client scripts, and code-copy buttons.
Setup
Install @void/md alongside your framework adapter:
npm install @void/md @void/reactnpm install @void/md @void/vuenpm install @void/md @void/sveltenpm install @void/md @void/solidAdd the plugin to your Vite config after the framework adapter:
import { voidMarkdown } from '@void/md/plugin';
import { voidReact } from '@void/react/plugin';
// vite.config.ts
import { defineConfig } from 'vite';
import { voidPlugin } from 'void';
export default defineConfig({
plugins: [voidPlugin(), voidReact(), voidMarkdown()],
});import { voidMarkdown } from '@void/md/plugin';
import { voidVue } from '@void/vue/plugin';
// vite.config.ts
import { defineConfig } from 'vite';
import { voidPlugin } from 'void';
export default defineConfig({
plugins: [voidPlugin(), voidVue(), voidMarkdown()],
});import { voidMarkdown } from '@void/md/plugin';
import { voidSvelte } from '@void/svelte/plugin';
// vite.config.ts
import { defineConfig } from 'vite';
import { voidPlugin } from 'void';
export default defineConfig({
plugins: [voidPlugin(), voidSvelte(), voidMarkdown()],
});import { voidMarkdown } from '@void/md/plugin';
import { voidSolid } from '@void/solid/plugin';
// vite.config.ts
import { defineConfig } from 'vite';
import { voidPlugin } from 'void';
export default defineConfig({
plugins: [voidPlugin(), voidSolid(), voidMarkdown()],
});Page Anatomy
A markdown page has three optional parts: a script block, frontmatter, and the body.
<script>
import Counter from "../components/Counter.vue" with { island: "visible" }
</script>
---
title: Getting Started
description: Learn how to use Void
---
# Getting Started
Welcome to Void. Here's an interactive demo:
<Counter /><script>block: extracted before markdown compilation. Island imports usewith { island: "..." }syntax (see islands). Any other code in the block becomes a client script that runs in the browser.- Frontmatter: YAML metadata between
---fences. Layouts can read it throughuseFrontmatter(). - Body: standard markdown plus GFM. Uppercase tags such as
<Counter />reference imported components and render as islands.
Client Scripts
Use the <script> block for island imports and JavaScript that runs when the page loads:
<script>
import Counter from "./Counter.vue" with { island: "visible" }
import { format } from "date-fns"
document.querySelector('.date').textContent = format(new Date(), 'PPP')
</script>
# My Post
<Counter />
Published: <span class="date"></span>Only the current page's client script loads. Pages without client code need no additional script.
File Structure
Markdown pages live in pages/ alongside regular pages and route the same way:
- pages/
- layout.island.tsx
- index.island.tsx
- docs/
- layout.island.tsxdocs layout (sidebar, TOC)
- getting-started.md
- configuration.md
- guides/
- deployment.md
A .md file inherits layouts, supports companion .server.ts files for dynamic data, and auto-prerenders when static. The rules are the same as any other page.
Frontmatter Access
Use useFrontmatter() in layout components to read the current page's frontmatter:
import { useFrontmatter } from '@void/md';
export default function DocsLayout({ children }) {
const fm = useFrontmatter();
return (
<div>
<h1>{fm.title}</h1>
{children}
</div>
);
}<script setup>
import { useFrontmatter } from '@void/md';
const fm = useFrontmatter();
// fm.title, fm.description, etc.
</script>
<template>
<h1>{{ fm.title }}</h1>
<slot />
</template><script>
import { useFrontmatter } from "@void/md";
const fm = useFrontmatter();
</script>
<h1>{fm.title}</h1>
<slot />import { useFrontmatter } from '@void/md';
export default function DocsLayout(props) {
const fm = useFrontmatter();
return (
<div>
<h1>{fm.title}</h1>
{props.children}
</div>
);
}Pages Virtual Module
Import @void/md/pages to get metadata for all markdown pages at build time. Use it to build sidebars, navigation, or search indexes:
import pages from '@void/md/pages';
// [{ path: "/docs/getting-started", title: "Getting Started", frontmatter: {...}, headings: [...] }, ...]Each entry has this shape:
interface MdPage {
path: string; // route path
title: string; // from frontmatter.title or first h1
frontmatter: Record<string, unknown>; // full parsed frontmatter
headings: { depth: number; slug: string; text: string }[]; // extracted headings
}The array is sorted by path and updates on HMR in dev when .md files are added, removed, or changed.
Default CSS Theme
The markdown plugin provides a minimal CSS theme with two entry points:
Full theme (reset + baseline + content)
For standalone markdown sites that need a complete stylesheet, use this package. It includes a modern CSS reset, baseline body styles, and all markdown content styles:
@import '@void/md/theme.css';Content only (scoped to .void-md)
For embedding markdown in an existing app that already has its own reset and global styles, use this one. It only includes the .void-md-scoped content styles:
@import '@void/md/theme-content.css';Usage
Wrap your markdown content in a .void-md element to scope the styles:
<main class="void-md">
<slot />
</main>The theme covers:
- Prose: headings, paragraphs, lists, blockquotes, tables, inline code, links, horizontal rules, task lists,
<kbd>,<mark>, definition lists, and footnotes. Dark mode works throughprefers-color-schemeand thedata-themeattribute. - Code blocks: Shiki dual-theme highlighting with CSS-driven switching and zero JS.
- Containers: styles for
:::tip,:::warning,:::danger,:::info, and:::details. - GitHub alerts: support for
> [!NOTE],> [!TIP],> [!WARNING], and similar syntax.
CSS Variables
All styles are customizable via CSS variables set on .void-md. Override them to match your brand:
.void-md {
--vmd-link: #8b5cf6;
--vmd-link-hover: #7c3aed;
}| Variable | Light default | Dark default | Description |
|---|---|---|---|
--vmd-font-body | system-ui, -apple-system, "Segoe UI", Roboto, sans-serif | default | Body font stack |
--vmd-font-mono | ui-monospace, "Cascadia Code", "Source Code Pro", Menlo, Consolas, monospace | default | Monospace font stack |
--vmd-text | #1a1a2e | #e2e8f0 | Primary text color |
--vmd-text-muted | #64748b | #94a3b8 | Secondary/muted text (blockquotes, line numbers, footnotes) |
--vmd-link | #2563eb | #60a5fa | Link color |
--vmd-link-hover | #1d4ed8 | #93bbfd | Link hover color |
--vmd-border | #e2e8f0 | #334155 | Borders (h2 underline, tables, inline code, <kbd>, horizontal rules) |
--vmd-bg-soft | #f8fafc | #1e293b | Soft background (table headers, inline code, code blocks, <kbd>) |
--vmd-line-height | 1.75 | default | Body line height |
Dark mode values apply automatically via prefers-color-scheme: dark (auto mode) and [data-theme="dark"] (explicit toggle). To force light mode on a dark-preference system, set data-theme="light" on <html>.
Users who want full control can skip the import and write their own CSS.
Markdown Features
Markdown features render as static HTML. Code-copy buttons add a small client script.
Containers
::: tip
Helpful advice here.
:::
::: warning
Watch out for this.
:::
::: danger
This will break things.
:::
::: info
Some context.
:::
::: details Click to expand
Hidden content here.
:::Custom titles work too: ::: tip Pro Tip.
GitHub Alerts
> [!NOTE]
> Useful information.
> [!TIP]
> Helpful advice.
> [!IMPORTANT]
> Key information.
> [!WARNING]
> Potential issues.
> [!CAUTION]
> Dangerous actions.Syntax Highlighting
Code blocks use Shiki with github-light and github-dark themes by default.
Line Highlighting
Highlight specific lines with {lines} in the code fence meta:
```ts {1,3-5}
const a = 1; // highlighted
const b = 2;
const c = 3; // highlighted
const d = 4; // highlighted
const e = 5; // highlighted
```Diff, Focus, and Error Levels
Use inline comments to annotate lines:
export function hello() {
console.log('old');
console.log('new');
console.log('look here');
console.log('problem');
console.log('careful');
}Code blocks include a copy button.
Line Numbers
Enable per code block with :line-numbers:
```ts :line-numbers
const a = 1;
const b = 2;
```Start from a specific number with :line-numbers=5.
Emoji
Shortcodes convert to unicode: :tada: becomes 🎉, :rocket: becomes 🚀.
Footnotes
Reference a footnote inline and define it anywhere in the document:
Here is a statement with a footnote.[^1]
[^1]: And here is the footnote body.The references and definitions render into a linked footnote list at the end of the content.
Attributes
Add classes, IDs, or attributes to any element:
# Heading {.custom-class #my-id}
Paragraph with attributes. {.note}Navigation and Media
Headings include permalink anchors. Use [[toc]] for a table of contents. Images are lazy-loaded, and .md links resolve to page routes.
Plugin Options
voidMarkdown({
shiki: {
themes: { light: 'github-light', dark: 'github-dark' },
},
});| Option | Type | Default | Description |
|---|---|---|---|
shiki.themes | { light: string; dark: string} | { light: "github-light", dark: "github-dark" } | Shiki color themes |
Code block languages load on demand from Shiki's bundled languages.
Building a Sidebar
Use @void/md/pages to select the pages for your sidebar:
import pages from '@void/md/pages';
const docPages = pages.filter((page) => page.path.startsWith('/docs/'));Render a link with page.path and page.title for each entry. Use useFrontmatter() in the layout to display the current page's metadata, and wrap its content in .void-md when using the provided theme.