Skip to content
Built with

Markdown ​

Build & Deploy

Add .md files to pages/ to make Markdown routes. They use your layouts and render as HTML, with JavaScript only for islands, client scripts and copy buttons.

On this pageTurn on Markdown pages · Use frontmatter and layouts · Add interactive islands

Setup ​

This step installs @void/md beside your framework adapter.

sh
npm install @void/md @void/react
sh
npm install @void/md @void/vue
sh
npm install @void/md @void/svelte
sh
npm install @void/md @void/solid

Example output: npm reports the packages added.

This step adds the plugin to your Vite config, after the framework adapter.

ts
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()],
});
ts
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()],
});
ts
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()],
});
ts
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.

md
<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 use with { 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 through useFrontmatter().
  • Body: standard markdown plus GFM. Uppercase tags such as <Counter /> reference imported components and render as islands.

Client scripts ​

This step adds island imports and page-load JavaScript in a <script> block.

md
<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 and supports .server.ts files for dynamic data. It auto-prerenders when static. The rules are the same as any other page.

Frontmatter access ​

This step reads the page's frontmatter in a layout with useFrontmatter().

tsx
import { useFrontmatter } from '@void/md';

export default function DocsLayout({ children }) {
  const fm = useFrontmatter();
  return (
    <div>
      <h1>{fm.title}</h1>
      {children}
    </div>
  );
}
vue
<script setup>
import { useFrontmatter } from '@void/md';

const fm = useFrontmatter();
// fm.title, fm.description, etc.
</script>

<template>
  <h1>{{ fm.title }}</h1>
  <slot />
</template>
svelte
<script>
import { useFrontmatter } from "@void/md";

const fm = useFrontmatter();
</script>

<h1>{fm.title}</h1>
<slot />
tsx
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 ​

This step imports @void/md/pages for build-time metadata on every Markdown page. Use it for sidebars, navigation or search.

ts
import pages from '@void/md/pages';
// [{ path: "/docs/getting-started", title: "Getting Started", frontmatter: {...}, headings: [...] }, ...]

Each entry has this shape:

ts
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. In dev, it updates on HMR when .md files change.

Default CSS theme ​

The markdown plugin provides a minimal CSS theme with two entry points:

Full theme (reset + baseline + content) ​

Use this for a standalone Markdown site. It has a CSS reset, base body styles and all content styles.

css
@import '@void/md/theme.css';

Content only (scoped to .void-md) ​

Use this inside an app that has its own reset and global styles. It has only the .void-md content styles.

css
@import '@void/md/theme-content.css';

Usage ​

This step scopes the styles. Wrap your Markdown in a .void-md element.

html
<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 through prefers-color-scheme and the data-theme attribute.
  • 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 ​

Every style uses a CSS variable on .void-md. Override them to match your brand.

css
.void-md {
  --vmd-link: #8b5cf6;
  --vmd-link-hover: #7c3aed;
}
VariableLight defaultDark defaultDescription
--vmd-font-bodysystem-ui, -apple-system, "Segoe UI", Roboto, sans-serifdefaultBody font stack
--vmd-font-monoui-monospace, "Cascadia Code", "Source Code Pro", Menlo, Consolas, monospacedefaultMonospace font stack
--vmd-text#1a1a2e#e2e8f0Primary text color
--vmd-text-muted#64748b#94a3b8Secondary/muted text (blockquotes, line numbers, footnotes)
--vmd-link#2563eb#60a5faLink color
--vmd-link-hover#1d4ed8#93bbfdLink hover color
--vmd-border#e2e8f0#334155Borders (h2 underline, tables, inline code, <kbd>, horizontal rules)
--vmd-bg-soft#f8fafc#1e293bSoft background (table headers, inline code, code blocks, <kbd>)
--vmd-line-height1.75defaultBody line height

Dark mode applies through prefers-color-scheme: dark and [data-theme="dark"]. To force light mode on a dark-preference system, set data-theme="light" on <html>.

For full control, skip the import and write your own CSS.

Markdown features ​

Markdown features render as static HTML. Code-copy buttons add a small client script.

Containers ​

md
::: 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 ​

md
> [!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:

md
```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:

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

md
```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:

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

md
# Heading {.custom-class #my-id}

Paragraph with attributes. {.note}

Headings include permalink anchors. Use [[toc]] for a table of contents. Images are lazy-loaded, and .md links resolve to page routes.

Plugin options ​

ts
voidMarkdown({
  shiki: {
    themes: { light: 'github-light', dark: 'github-dark' },
  },
});
OptionTypeDefaultDescription
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 ​

This step picks sidebar pages from @void/md/pages.

ts
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 for the page's metadata. With the provided theme, wrap content in .void-md.

Next steps ​

implemented.stream AI